useState and useReducer dispatch unstable between renders
kavika-1 opened this issue · comments
Using a library that works in React/Preact, but not in fre. Upon debugging, saw this code:
const [embla, setEmbla] = useState<EmblaCarouselType>()
...
useEffect(() => {
... setEmbla(...)
}, [..., setEmbla])
The useEffect
gets triggered unexpectedly, which is any time the function component (re)renders. It appears to be due to setEmbla
being assigned a new function, thus triggering the useEffect
again. Understandable why the library writers did this, since rules of hooks requires adding anything used in the useEffect
to be declared in the dependencies array.
Does it make sense to address this?
Maybe it's meaningful. I don't see that react has additional processing here. Can you write a demo use case? Let me see the specific situation first.
Just I am not familiar with fiber, so passing along... in React 17 old hooks looks like they use ReactCurrentDispatcher.current
:
https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js#L24
https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js#L82
In ReactFiberHooks.new, further along there are several references to ReactCurrentDispatcher.current
as well:
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.new.js#L2601
Maybe that helps? Otherwise, will post a contrived example below
Contrived example: Want to initialize a state in useEffect
. However, will make setState
a dependency of useEffect
since supposedly that is how rules of hooks wants us to behave.
Running the fre
version:
- Console reports "function component rendering" 94 times (this number varies)
- Upon clicking [+],
State: 100
never changes, it gets reset to100
- But - the
increment()
function reports it's own value incrementing
- But - the
- If
setState
is removed from the dependency array, everything works fine
Running the react@17
version (comment out // fre
line, and uncomment // react lines
- works fine with or without
setState
in the dependency array
<!DOCTYPE html>
<html lang="en">
<body>
<div id="test"></div>
</body>
<script type="module">
// fre
import { render, h, useState, useEffect } from 'https://unpkg.com/fre@2.4.4/dist/fre.js'
// react
// await import ('https://unpkg.com/react@17.0.2/umd/react.development.js')
// await import ('https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js')
// const { render } = ReactDOM;
// const { createElement: h, useState, useEffect } = React;
function Test() {
console.log('function component is rendering')
const [state, setState] = useState(0)
useEffect(
() => {
setState(100)
},
[setState]
)
function increment() {
setState(v => {
console.log('increment to', v + 1)
return v + 1
})
}
return (
h('div', {}, [
h('p', {}, `State: ${state}`),
h('button', { onClick: increment }, '+')
])
)
}
const el = document.getElementById('test')
render(h(Test, {}), el)
</script>
</html>
Screenshot when running fre
- note the state does not increment in the DOM, but still does in setState
...
Screenshot when running react@17
(or fre
without setState
in the useEffect
deps)
Thank you. I fixed it. setState
should only be initialized once.
Please update 2.4.5
I can confirm that the actual real component(s) now work as well. That's incredible turn-around time 👍 🥇 Thank you!