Source issue - https://github.com/gaearon/react-hot-loader/issues/1262
One of the biggest issues for React-Hot-Loader in the last past months were hooks, which were not working properly, especially from the hot-loading point of view.
Version 4.9.0 introduced the fix for it via adding one more argument to every hook with dependencies, like useEffect
.
useSynchronousEffect
is split into two parts - apply the effect, and remove it.
useEffect
, and got called during hot updatefunction useSynchronousEffect(func, values) {
const ref = React.useRef([]); // THIS REF WOULD BE PRESERVED
let output;
// THIS WOULD NOT BE RE-RUN ON HOOK UPDATE
if (ref.current.length !== values.length) {
ref.current = values;
output = func();
} else {
for (let i = 0; i < values.length; i += 1) {
if (values[i] !== ref.current[i]) {
ref.current = values;
output = func();
break;
}
}
}
React.useEffect(
() => () => {
if (output) {
output(); // WILL BE RESET ON EFFECT RE-RUN
}
},
values, // eslint-disable-line react-hooks/exhaustive-deps
);
}
This issue should be solved regardless of the nature of hooks hot reloading - a special logic like this should be a bit more reliable.
values.length
expected to never be changed during the component life cycle (rules of hooks)useSynchronousEffect
to useEffect
on clientside (the best option)useMemo
to check stored values, but it still not quite stable, as long useMemo
light drop the cache, and it makes this hooks more complex.function useSynchronousEffect(func, values) {
const lastRef = React.useRef([]);
const currentValues = React.useMemo(values=> values, values); // value would be reset on hot-update
let output;
if(ref.current !== currentValues){
ref.current = values;
output = func();
}
React.useEffect(
() => output,
values, // eslint-disable-line react-hooks/exhaustive-deps
);
}
Let me elaborate more on this:
setState
, and useRef
.useMemo
, useCallback
_should_ be considered as derived data generators and could be rerun at any time.useEffect
in any form is a side effect with not deterministic execution moment (no execution on a server side for example), thus should be _good to have_.constructors
and destructors
always should be paired.useEffect
might have a destructor
.PS: useState
theoretically could be updated on HMR, but there is no way to update(not reset) useRef
.
function useSynchronousEffect(func, values) {
const [key] = useRef({});
let output;
// store "generation" key. Just returns a new object every time
const currentKey = useMemo(() => {}, values); // eslint-disable-line react-hooks/exhaustive-deps
// "the first render", or "memo dropped the value"
if(key.current !== currentKey){
ref.current = currentKey;
output = func();
}
React.useEffect(
() => output, // there is no need of a complex construction here.
[currentKey], // eslint-disable-line react-hooks/exhaustive-deps
// ^ use the same variable here.
);
}
Use effect
on SSR, and switch to useEffect
on CSR.
const useSynchronousEffect = isServer ? (effect, props) => effect() : useEffect
The problem - there is no way to detect is this "Server" or "Not". It's not bound to the environment, but to the __react command__ used to kick off the render. Probably it should be
function useSynchronousEffect(func, values) {
const isServer = React.useContext(IsServer); // But still - who will set this value?
isServer ? effect : useEffect(effect, values);
}
I'm looking into it.
@oliviertassinari Hey Oliver, have you had any chance to look into this yet? This has been bugging me in the current project I am working on.
I've doublechecked this behavior with upcoming React Fresh, and it would be the same.
So we have to find a solution, compatible with these constraints, but I am not sure that MUI shall do it by their own, as long as any project might need that _Synchronous Effect_, in the same way MUI needs it.
@theKashey Thanks for checking! I haven't looked into it yet, to be honest, but I hope we can release a v4.1.1 tomorrow with a fix and the TypeScript icon regression fix. This hook logic also has an issue with the Concurrent Mode, the problem is related.
It's fixed in https://github.com/mui-org/material-ui/pull/16195 and will be soon released in Material-UI v4.1.1.
Most helpful comment
I'm looking into it.