useProxy is executed twice on sub-tree state update
laurencefass opened this issue Β· comments
π Bug report
π€ Current Behavior
useProxy is executed twice on sub-tree state update
I have sub-components subscribed to slices of state with both useSelector and useProxy. The useProxy is executing twice.
state
export const APP_STATE = createState({
slice1: {
value: 0
},
slice2: {
value: 0
},
});
subsciber components
xport const Slice1Proxy = () => {
const state = useProxy(APP_STATE);
useEffect(()=>{
console.log("Slice1Proxy.mySlice1", state.slice1); //TEST FAIL: executes twice per update
},[state.slice1]);
return (<></>)
}
export const Slice1Selector = () => {
const mySlice1 = useSelector(APP_STATE, (state) => state.slice1);
useEffect(()=>{
console.log("Slice1Selector.mySlice1", mySlice1); // TEST PASS: executes once per update
}, [mySlice1]);
return (
<></>
)
}
π― Expected behavior
useSelector and useProxy should execute only once per state update
π Reproducible example
https://codesandbox.io/s/agilets-proxy-kj7oo?file=/src/core.js
π‘ Suggested solution(s)
β Additional notes
π» Your environment
See codesandbox versions
Seems like the 'double' re-render comes from the fact,
that you have subscribed the APP_STATE
not only to the child
Components,
but also to the parent
Component.
-> The child Component is re-rendered once by the useProxy()
Hook
and the parent Component is also re-rendered once by the useAgile()
Hook
export const Subscriber = () => {
const myState = useAgile(APP_STATE); // <- re-renders the 'Subscriber 'Component when `APP_STATE` changes
useEffect(()=>{
console.log("Subscriber.myState", myState);
},[myState]);
return (<>
<Slice1Selector/>
<Slice1Proxy />
</>);
}
The solution was to simply not bind the APP_STATE
to the parent Component.
See code sandbox:
https://codesandbox.io/s/agilets-proxy-forked-jdbcd?file=/src/core.js
The wired thing is now why the Component with the useSelector()
wasn't re-rendered twice. π€
I agree that we can can remove the apparent error by removing const myState = useAgile(APP_STATE)
from the parent but Im not sure its a solution and Im a bit confused about how and why it works this way.
The parent useEffect() is only called once per update indicating a single render, not two.
useEffect(()=>{
console.log("Subscriber.myState", myState); // called once per update
},[myState]);
I cant reason why the proxy update would be - or need to be - called twice.
Generally there may be uses-cases where discrete state updates are required across parents and children. Is there a best practice guide for doing this AND eliminiting duplicate updates?
I agree that we can can remove the apparent error by removing const myState = useAgile(APP_STATE) from the parent but Im not sure its a solution and Im a bit confused about how and why it works this way.
I guess it is with the current AgileTs version the only solution to prevent a double re-render.
The parent useEffect() is only called once per update indicating a single render, not two.
Yes exactly, the parent useEffect()
is called once because useAgile()
triggers a re-render on the parent Component
when the APP_STATE
updates.
Through the parent re-rendered, all children are re-rendered too.
-> The first
re-render in Slice1Proxy
comes from the parent.
Since we have bound a part of the APP_STATE
to the child Component too,
the Child Component is also re-rendered (apart from the parent)
when the selected APP_STATE
property updates.
That is because AgileTs and the Child Component don't know anything about their parent Component/s.
-> The second
re-render in Slice1Proxy
comes from itself.
Generally there may be uses-cases where discrete state updates are required across parents and children. Is there a best practice guide for doing this AND eliminiting duplicate updates?
If that is the case, why not only binding the State to the parent Component?
You can also access the State value without binding it to the particular Component that requires the State value
by using the State's value
property.
// Doesn't re-render when MY_STATE changes
const myState = MY_STATE.value;
// Re-renders when MY_STATE changes
const myState = useAgile(MY_STATE);
AgileTs:
https://codesandbox.io/s/httpsgithubcomagile-tsagileissues179agile-bnktp?file=/src/core.js
Redux-Toolkit:
https://codesandbox.io/s/httpsgithubcomagile-tsagileissues179redux-c5szd?file=/src/core.js
In Redux-Toolkit it only re-rendered once, since they re-render the Component via the Context API (which is wrapped globally around all Components)
and not for each Component separately, like AgileTs does.
Will close the issue since it is the expected behavior
that each binding to a Component (no matter if parent or child) results in a re-render (for each Component)
when the bound State mutates.
fixed the unwanted side-effect with a memo() wrapper round the whole component. proxies are awesome. thanks for your help and advice.