Skip to main content

React useState warning to save me a day

· 3 min read

Why

I have used a 3d party library and re-assigned the same object to the state. This caused the state to not update and the component to not re-render. I spent a day debugging this issue.

I would have enjoyed the debug build of React to warn me when assigning the same object to the state. This would have saved me a day. It seems you don't want do do this on purpose, so it would be a good warning to have.

const [room, setRoom] = useRoom(new RoomFrom3dPartyDependency());

useEffect(() => {
room?.onStateChange((stateReference) => {
// WARNING: State object remains the same throughout room's lifetime.
// If we simply pass stateReference to setRoomState
// react will be tricked into thinking the state has not changed
// and our component will not be re rendered.
setRoomState(Object.assign({}, stateReference));
});
}, [room]);

How

I will look into the React source code to see if I can add this warning myself. [NOT DONE] I will also look into the React issue tracker to see if this is already being worked on.

What

  • [Ideally] A PR to the React codebase that adds a warning when the same object is assigned to the state.
  • [Reality] I have taken some notes, browsed React GitHub repo, have not run React from source and abandoned the idea of a PR :D, finally I am writing this mini report as a way to practice writing about my work, even that has not bore fruit.

Code pointers in React

useState definition https://cs.github.com/facebook/react/blob/577f2de46cb2f6d71380551478a02de3965a764b/packages/react-reconciler/src/ReactFiberHooks.new.js#L2621

returns mountState(initialState) [S, Dispatch<BasicStateAction<S>>] https://cs.github.com/facebook/react/blob/577f2de46cb2f6d71380551478a02de3965a764b/packages/react-reconciler/src/ReactFiberHooks.new.js#L1501

.bind(newThis, arg1, arg2) -> new partial function with bound this

mountState returns [intialStateSnapshot: S, dispatchSetState: S -> ()] dispatchSetState was partially applied to only leave one arg left to apply (the user does it)

This is probably why we inject all the needed globals like fiber (instead of currentlyRenderedFiber) and updateQeue. UpdateQueue is what runs updates specifically for this hook I think, so if there are multiple setX called fast. Because it is executed whenever the user wants it to

This talks about bailing out!! Seems important https://cs.github.com/facebook/react/blob/577f2de46cb2f6d71380551478a02de3965a764b/packages/react-reconciler/src/ReactFiberHooks.new.js#L2234

Also update.action is the actual state, it gets passed to enqueueUpdate https://tinyurl.com/y98l9w5q. This is done before bailing out, which is interesting. Its almost like we want to keep this update info, even tho there is nothing to do just to mark that we did it? Confused.. Based on this assumption if we have a single update to the component with this not updated state we will run the rerender (fuck obviously lol)

I think this is where the early return happens https://cs.github.com/facebook/react/blob/577f2de46cb2f6d71380551478a02de3965a764b/packages/react-reconciler/src/ReactFiberHooks.new.js#L2258

Because it never gets to this line https://cs.github.com/facebook/react/blob/577f2de46cb2f6d71380551478a02de3965a764b/packages/react-reconciler/src/ReactFiberHooks.new.js#L2270

OLD impelemntation, liternal in file name CHECK FILE NAME BRO WTF

renderWithHooks https://tinyurl.com/y7q8sxw9 useState that is used for update dispatcher - for different component lyfecycle different dispatches are used https://tinyurl.com/yaldt48p

When this was forked old / new version 17 https://github.com/facebook/react/commit/17f582e0453b808860be59ed3437c6a426ae52de

Apparently new reconciler is not rolled out yet! Initiated work here https://github.com/facebook/react/pull/18285

feature flag enableNewReconciler in packages/shared/ReactFeatureFlags.js