fix(ios): blank RiveView after Fabric recreates the component view (react-freeze) — props skipped by consumed isDirty flags - #365
Conversation
|
cc @rive-engineering @HayesGordon for review 🙏 |
|
@rlods Thanks for the detailed write-up — I reproduced this and your diagnosis is spot on. Note it also reproduces with a plain Your patch does fix it (verified on both paths, iOS harness green), but it modifies a generated
|
thanks @mfazekas i'm totally aligned that upstream fix is the way to go my PR was just a light temporary fix until nitro was upgraded in this repo which has much bigger impact do you plan to handle the nitro upgrade ? in the meantime, I'll integrate second option in this PR (I didn't know about |
When Fabric recreates a component view from an unchanged ShadowNode (e.g. react-freeze/Suspense re-inserting a previously hidden screen), the shared Props object's isDirty flags were already consumed by the previous view instance, so updateProps applied nothing: the fresh HybridRiveView never received file/artboardName/hybridRef and rendered blank forever. Force-apply every prop on a view instance's first updateProps, keeping the isDirty fast path for subsequent updates.
…urvives regeneration CI regenerates nitrogen/generated/ and fails on any diff, so the force-apply-on-first-updateProps patch cannot live only in the generated HybridRiveViewComponent.mm. Apply it from scripts/nitrogen-postprocess.ts (alongside the existing nitro#1184 patch) so `yarn nitrogen` reproduces the committed file byte-for-byte. Also make acceptNullForOptionalProps idempotent: it used to re-insert its null-check lines when the script ran twice without regenerating first. The patch is fixed upstream in nitro 0.37 (mrousavy/nitro#1503, #1506, mrousavy/nitro#1510) — both postprocess patches can be dropped on the next nitro upgrade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8836352 to
6450982
Compare
The top-level guard already returns when the file contains the value.isNull() marker, so the inner re-check could never be true. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Symptom
On iOS (new architecture), a
RiveViewrenders permanently blank after navigating away from its screen and coming back, when the app usesreact-native-screenswithenableFreeze(true)(react-freeze / Suspense). The native view is laid out (background color, size all correct) but nothing is ever drawn, andplay()/playIfNeeded()on the ref silently do nothing. Android is unaffected.Root cause
When a screen is frozen, Fabric deletes its native views; on return it recreates them from the same, unchanged ShadowNodes — calling
updateProps:oldProps:on the brand-new component view with the same cachedPropsobject as before.The nitrogen-generated
HybridRiveViewComponent.mmguards every prop withisDirtyand, after applying it, mutates the shared props object (newViewProps.file.isDirty = false;viaconst_cast). Those flags were already consumed by the previous view instance, so on recreationupdatePropsapplies nothing:HybridRiveViewkeeps its default emptyHybridRiveFile, soafterUpdate()bails atguard let file = hybridFile.riveFile else { return }—configure()never runs, noRiveViewsubview is ever created (we confirmed with a view-hierarchy inspector: the recreatedRiveReactNativeViewhas zero children);hybridRefnever re-fires, so JS still holds the ref to the dead old hybrid — which is whyplay()resolves fine but does nothing (baseViewModel?.play()on nil).Nothing ever marks the props dirty again unless the JS
fileobject changes identity, so the view stays blank forever.This is the mirror image of mrousavy/nitro#1050: there, a recycled view keeps the old props; here, a recreated view gets no props at all. Reproduced on
@rive-app/react-native@0.4.19; the generated code is identical onmainand in0.5.0-beta.1.Fix
Track
_didApplyInitialPropson the component view and force-apply every prop on the firstupdatePropsof each view instance, keeping theisDirtyfast path for all subsequent updates. First-ever mounts are unaffected (all flags are dirty there anyway); recreated views now get their full configuration andhybridReffires with the new hybrid.We've been running this as a patch in our app (large health-insurance app, several Rive scenes behind frozen tab/stack screens) and it fixes the blank-view repro deterministically.
Note on the generated file
I'm aware this file is nitrogen-generated (
DO NOT MODIFY) — the durable fix likely belongs in nitrogen's view-component template (and would then coverframeRate/semantics/onStopand any future props automatically, as this PR'sforce ||does). Opening this here since the generated sources are committed to this repo and ship in the npm package; happy to close in favor of an upstream nitrogen fix if you prefer to route it there.Repro
react-native-screens,enableFreeze(true)<RiveView>; push any screen B on top (freeze unmounts A's native views)RiveViewis blank;play()no-ops; only afileidentity change revives it🤖 Generated with Claude Code