Component render previous value #2495
Replies: 8 comments 1 reply
-
I would advise you against a global store in such a case. Instead of resetting just create a new one when appropriate and pass down through props. |
Beta Was this translation helpful? Give feedback.
-
Thanks for response! IMO it's not always an option. Even though, re-creating a new instance still won't work. A silly example: useEffect(() => {
return () => {
state = observable({ value: "from home" }); // the same behaviour
};
}, []); To solve this I create a dummy hook to somehow patch the router: export function usePageCleanup(cleanupFn: () => void) {
const history = useHistory();
useEffect(() => {
const historyListener = history.listen(() => {
cleanupFn();
});
return () => {
historyListener();
};
}, []);
} I assume it runs before than traditional useEffect.cleanup and as result, the new rendered component received an updated state. |
Beta Was this translation helpful? Give feedback.
-
The react mounts/unmounts component if the rendered tree differs ... logically you first have to return new tree (render page B), so it can diff it with previous one. Then it finds out the component A was replaced by component B and runs cleanup for A. I am not familiar with react router so I won't tell you how to do that, but you either have to
// must be layout effect
useLayoutEffect(() => {
resetState()
}, [])
// The first render will see the old state
// so you may want to render it conditionally
if (isStillOldState) return null; // don't worry this won't ever be flushed to screen But why do you need to reset the state? Why don't you simply have 2 local states? |
Beta Was this translation helpful? Give feedback.
-
@urugator thank you for your response! First the questions:
For historical and convenient reasons, I guess. Let's see almost every from the mid-size app has something which we've named as "global state". Auth, UI, etc. states. Redux/Mobx/MST solve it with either one "global state" or "root store". And there is a high probability of changing the slice of it and render the updated version.
The single responsibility principle works here, and in IMO it should be a clear separation of concern as you've mentioned. About:
The approach listening to events and patch the behavior is valid. But is ridiculous, because you need to match the routing path.
The same mental model was in my had as well. So I've tried a bit silly approach - wait when the component is mounted and only after doing a presentation logic. export function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
} The working example - https://codesandbox.io/s/laughing-yonath-ico78?file=/src/App.js |
Beta Was this translation helpful? Give feedback.
-
I didn't mean attaching an event listener, but intercepting the function that does the navigation. function navigate(router, page) {
// reset state or whatever
resetState(page)
// actually navigate
router.navigate(page)
}
I mean https://reactjs.org/docs/lifting-state-up.html If you want to keep page related state inside a global state I suggest doing it like this: const globalState = {
// this could be an array, but we want the lookup by id be fast and simple
pages: {
home: {
id: 'home',
title: 'Home',
},
products: {
id: 'products',
title: 'Products',
},
},
}
// action
setFoo(pageId, foo) {
globalState[pageId].foo = foo;
}
// in render
const pageState = globalState[router.currentPageId]; Do not place all state that is needed inside a page on const globalState = {
pages: {
home: {
id: 'home',
title: 'Home',
// A page will refer to tabs by id
tabs: ['home-description', 'home-images'],
},
/* ... */
},
tabs: {
// The id is just for illustration
// The idea is NOT to make tabId derivable from pageId
// A page refers to tab by id explicitely as seen above
'home-description': {/* ... */},
'home-images': {/* ... */},
'products-prizes': {/* ... */}
},
} The idea is that for each unique (and reusable) state shape, you have a collection (pages,tabs) in global state - exactly like tables in sql db. It may seem a bit inconvinient/redundant, but it scales very well and avoids all sorts of problems. |
Beta Was this translation helpful? Give feedback.
-
Still, we're trying to fix the consequences nor the problem.
There're plant techniques on how we can deal with "GlobalState". AST and RootState, Mobx and RootStore pattern is one of them in the world of mobx. There's a clear separation of domain entities. There is an interesting release note from React team about react@17. The direct statement:
Which is exactly what I was expected, but as turned out it works differently with a combination of routers. |
Beta Was this translation helpful? Give feedback.
-
But changing the state as a side effect of render is the problem. It's an antipattern as it breaks one way data flow. The react wants you to avoid this by lifting state up.
All I can tell is that whenever I need to synchronize multiple "states" (if I can't merge them together), I find it more robust and maintainable if I do it inside a single action, rather than doing it ad hoc in effects or reactions.
I don't think it has anything to do with this ... It doesn't matter whether the clean up runs synchronously or not it always runs after the new component is rendered. It's just that the cleanup function doesn't block the browser from repainting. |
Beta Was this translation helpful? Give feedback.
-
Moving to discussions |
Beta Was this translation helpful? Give feedback.
-
Preconditions:
The use case:
Problem:
In .5 component B renders an old state { value: 0 } (before cleanup in component A).
After it reacts for changes for second rerender it has a new state { value:1 }.
Demo
https://codesandbox.io/s/adoring-fog-dsvew?file=/src/App.js
I bet either I'm doing smth wrong or I don't understand how it's possible to clean/reset global stores/states.
I'd appreciate any feedback.
Beta Was this translation helpful? Give feedback.
All reactions