React js 18 : suspense does not [always] reset state of suspended components
React version: 18.1, probably 18.2 as well but can't chose it in CodeSandbox
Exhibit A
Steps To Reproduce
- Open https://codesandbox.io/s/nice-danilo-ej7mlr?file=/src/App.js, click on "Add Resource" to create resource
The current behavior
This is a variation on the Concurrent Mode documentation sample, but without the Suspense wrapping the consumer of resource. Clicking on the button sets state of the FirstChild component, resource
variable is truthy and SecondChild is rendered, which reads resource and throws out a promise, and suspends (is this the right term?) both FirstChild and SecondChild as SecondChild is not wrapped in Suspense. After the promise is resolved, both FirstChild and SecondChils are rendered.
The expected behavior
According to the documentation, previous issues and common sense (setting state is not inside Transition), both FirstChild and SecondChild are unmounted when Suspense shows fallback, so state of the FirstChild should be reset. Instead, it somehow preserves the resource inside state and renders SecondChild.
Exhibit B
Steps To Reproduce
- Open https://codesandbox.io/s/funny-pateu-z4cr8t?file=/src/App.js, click on "Refresh"
The current behavior
This is a modification of the previous sandbox, but resource is first created outside of FirstChild and can be refreshed, without Transition and even inside setTimeout so that React can't be smart and track it as event handler. Additional state randomValue
is present that is preserved after unmounting and mounting again.
The expected behavior
As previously, I expect that state is reset, and randomValue
is a new number on every refresh. Same result if using useMemo
or useRef
.
Exhibit C
Steps To Reproduce
The current behavior
Code is stuck in an infinite loop. A new resource is created not outside of component and not inside event handler, but during render. With everything else equal, and contrary to the previous two examples, it (apparently) does not save the state that has resource but resets it.
The expected behavior
Biiig question mark here. My expectations is that components inside Suspense are fully unmounted and lose all state, hence we need to either create resources outside of render tree, or place state with resources above Suspense, aka wrap children in additional Suspense. But that does not seem to be the case in the first two examples. Which means I have no idea how it should behave and if it is undocumented feature or a bug. Also, useEffects
are also not running after unmounting-and-mounting, but useLayoutEffects
do, which seems in line with React 18 RFC.