Visitar URL original
StrictMode: problem rendering splat · Issue #308 · playcanvas/react · GitHub
Skip to content

StrictMode: problem rendering splat #308

Open

Description

@smoya-ekoo

When I use StrictMode, the splat disappears.

This happens because StrictMode re-renders the content.

Anything that re-renders the component containing the splat will cause it to disappear.

Activity

  1. abstrakt8 commented on Jan 30, 2026

    @abstrakt8
    Contributor

    @smoya-ekoo Just to confirm, you are using the latest versions? Which @playcanvas/react version (or playcanvas) are you using?

  2. smoya-ekoo commented on Feb 2, 2026

    @smoya-ekoo
    Author

    Yes I do :(

    "@playcanvas/react": "0.11.3",
    "@playcanvas/react": "0.10.2",
    "react": "^19.2.4"

  3. juancabrera commented on Feb 9, 2026

    @juancabrera

    @abstrakt8 I have the same issue with latest version, here is the error:

    Error applying prop id: TypeError: Cannot set property id of #<GSplatComponent> which has only a getter

    For some reason at re-render it tries to re-assign the id, which only has a getter. So it only renders the first time, if anything makes it re-render (at times needed) it errors and "disappear"

    happening with:
    @playcanvas/react: 0.11.3
    playcanvas:2.16.1

  4. abstrakt8 commented on Feb 10, 2026

    @abstrakt8
    Contributor

    @smoya-ekoo or @juancabrera Could you please provide a minimal reproducible example?

    @juancabrera Thanks, I also just noticed this right now - I think this was introduced recently due to this engine change in 2.16.0 https://github.com/playcanvas/engine/pull/8421/changes#diff-9b930b167e43f1eb0e8fe87e9e913825fd37efece4bdd24df384a445424f9205

    I'm also currently working on new examples and have a WIP branch where I test out the re-rendering issues, for instance it does not seem to appear in this example (which is wrapped in <StrictMode>):

    https://github.com/playcanvas/react/blob/new-examples-2/examples/src/pages/LodStreaming.tsx

  5. juancabrera commented on Feb 10, 2026

    @juancabrera

    @abstrakt8 that would make sense!

    I made a quick splat scene using the template (npm create playcanvas@latest -- -t react-ts), then added a splat and the error is reproduced, at first the skull splat will render, but on hover on the sphere it will disappear and the error would be in the console log

    3dgs-rerender.zip

    you should be able to just run :
    npm i
    npm run dev

  6. abstrakt8 commented on Feb 11, 2026

    @abstrakt8
    Contributor

    Thanks I had a look and could reproduce with this example. I will have a look when I get the chance!

  7. juancabrera commented on Feb 11, 2026

    @juancabrera

    @abstrakt8 nice!
    FWIW I did try older versions for both playcanvas and @playcanvas/react, going as back as thing started to break (gltf support broke), and the issue was still present. So I do not think is a recent change that caused the issue.

  8. cazaroni commented on Jul 12, 2026

    @cazaroni

    Ran into this on 0.11.5 + engine 2.20.6 (Viewer.Splat from @playcanvas/blocks, using a RTX 3050 GPU) and spent a while tracing it, so posting what I think it explains why this bug keeps coming back after fixes.

    TL;DR: it's not prop re-application in general. It's specifically the instance: null schema default clobbering the instance the engine creates on its own after the asset loads.

    The chain:

    1. useComponent's update effect has no dependency array, so every re-render re-applies the full validated prop set: use-component.tsx#L44-L60
    2. If you only pass asset (which is all Viewer.Splat passes), every other prop comes from schema defaults — including instance: null, on every render: GSplat.tsx#L80-L91
    3. The asset loads async, and when it does the engine sets component.instance = new GSplatInstance(...) itself (_onGSplatAssetLoad, non-unified path). React never sees this happen.
    4. On the next re-render from any cause (camera interaction, StrictMode double-render, parent state), the guard from fix(GSplat): Do not apply instance and material again if no changes. #304 checks instance.instance === value — live instance vs null → not equal → it assigns component.instance = null.
    5. The engine's instance setter calls destroyInstance() before storing the value. And it never recovers, because re-applying the same asset is dead on arrival in the engine (_assetReference.id equality check), so nothing recreates the instance.

    That matches every symptom in this thread: fine until the first interaction, StrictMode kills it instantly, silent since 0.11.4 (#326 removed the id throw that used to make it loud), and non-React viewers are unaffected. I reproduced identically on engine 2.14.0 and 2.20.6.

    The part I really wanted to flag: #298 doesn't fix this either. Its valuesEqual(liveInstance, null) is false and so the values genuinely differ. null ends up getting applied again. I checked against the PR's diff. Merging #298 alone will likely re-close this issue without actually fixing it.

    I've been running this locally on 0.11.5 (via pnpm patch) and the splat now survives any amount of orbiting on a real GPU:

    // GSplat schema, `instance.apply`
    apply: (instance, props, key) => {
        const value = props[key];
        if (instance.instance === value) return;
        // never apply the schema's null default over the live instance —
        // the engine creates it async after asset load, and
        // GSplatComponent#instance = null calls destroyInstance()
        if (value == null) return;
        instance.instance = value;
    }

    (plus a #298-style skip-unchanged guard for the general redundant-setter class. Seems like you need both.)

    Arguably the cleaner fix is to not apply schema defaults to engine-owned props at all (instance, material… and unified has the same problem: the schema defaults it to false while engine ≥2.18 defaults to true, so every <GSplat> silently runs the legacy non-unified path). The snippet above is just the minimal version; the trade-off is that an explicit instance={null} "clear" becomes a no-op.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions