-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Memoize navigation action functions #7631
Comments
It's supposed to be memoized, but there seems to be a bug: https://github.com/react-navigation/core/blob/e514a61f9314e8d771231de9d3a4ebf64f661003/src/getChildrenNavigationCache.js We definitely need to fix this. I'll take a look at it after RNEU. But if you have time to check it, it'll be great too. Though this code doesn't feel right to me. I assume they have a login screen or something and they want to navigate when the Correct code would be something like this (which also wouldn't be problematic due to useEffect(() => {
if (prevUserRef.current !== undefined && user !== undefined) {
navigate(...)
}
prevUserRef.current = user;
}, [user, navigate]) IMO if the code is behaving unexpectedly due to a dep changing, then there's an actual bug with the code which needs to be fixed. Caching |
Thanks for the link, didn't know about this caching system. Asked the user what's his intent with this code. Not sure what's the intent of your code either :D If you want to navigate after a login you could simply navigate from the login button callback instead of using an effect.
Not sure what you mean by that, and also not sure to agree. For me it's perfectly fine to navigate in an effect, and if you do, eslint will ask you to put navigate as a dependency so it should rather be stable (it would probably work fine if you just omitted it but people will complain due to eslint) Are we talking about "navigation" or "navigate" here? Because I think
|
It depends on the architecture. This pattern will be more about frameworks like Redux where the fetching happens in a middleware and the component just receives latest object. There's no callback.
It's simply saying if the
Yes it's perfectly fine to navigate in The fact that IMO the advantage of dependency array is that it's harder to write code assuming a prop never changes. Now, how often you need to run the effect shouldn't usually impact actual behaviour. If you're making a network request, you definitely want to minimize how many times you want to run that, but if doing multiple requests breaks the behaviour of the app, there's a bug. Reducing the network requests is an optimization here. In 5.x, the The reason we need to fix the caching is because it prevents pure component optimization. But I'd strongly recommend not to write code which assumes that the props never change. |
Ahhh yeah missed that, we don't want to navigate to authenticated screens everytime the user updates his username :) I'd rather use something like !!user / boolean for that instead.
Yeah I see, we'd like to avoid re-rendering screens uselessly on navigation change. But what about navigation actions (navigate/goBack). Currently, those functions are not stable, but I think it would be convenient for the users if we guaranteed stability. People could write: useEffect(() => {
if (!!user) {
navigate("AuthenticatedNavigator")
}
}, [!!user]) And it would normally work fine. But in practice, they'll have ESLint and just to comply to ESLint they'll put the navigate as dependency: useEffect(() => {
if (!!user) {
navigate("AuthenticatedNavigator")
}
}, [!!user, navigate]) And then we have a bug, that was actually caused by following ESLint recos. Users can still solve it in userland and stabilize the navigate function themselves, or add an eslint-ignore, but I think it would be more convenient to just ensure they can add navigate as a dependency and expect it to be stable. |
We could probably make them stable, depends on how the caching logic is written and we definitely should. But I still think the effect logic is wrong. User shouldn't rely on a prop being stable. I'm thinking this way, does the effect need to run if the Imagine if it wasn't |
For me it looks fine to navigate every time the user becomes available. If user logs in, logs out, and logs in again, the effect/navigate should execute again (actually most people will use SwitchNavigator which will unmount/remount the unauthenticated screens after login, but they may keep them mounted and if they do, that logic makes sense to me, otherwise they won't navigate after the 2nd login) We actually don't want this effect to kick in if the navigation.navigate fn changes, and personally I'd write this without the "navigate" in deps, assuming react-navigation does not guarantee stability (which is quite an implicit contract): useEffect(() => {
if (isAuthenticated) {
navigate("AuthenticatedNavigator")
} else {
navigate("UnauthenticatedNavigator")
}
}, [isAuthenticated]) I'm not using the hooks rules yet because I'm still on tslint in some projects, but I see how this rule of exhaustive deps can sometimes be annoying, and maybe even create bugs. Would you recommend the user to not follow the exhaustive deps rule like I do and not put the Or do you think we should always follow the ESLint rule and handle inside the effect the potential annoying side effects that this imply? Because personally I think it sure works but also complicates a bit the code, I'd rather not follow the ESLint rule and have simpler code. But in practice, not following the ESLint rule would mean we assume the navigate() function identity changes over time, but it's behavior is stable. ie we have navigate1 and navigate2 with 2 distinct identity, and we can call navigate1("X") or navigate2("X") and it would lead to the same result. For that we must be sure that the navigate closure does not capture any state that would lead to different results |
Also it's possible to use a "navigateRef" which can be stable and ensure to always return latest navigation object: const { navigationRef } = useNavigation();
useEffect(() => {
if (isAuthenticated) {
navigationRef.current.navigate("AuthenticatedNavigator")
} else {
navigationRef.current.navigate("UnauthenticatedNavigator")
}
}, [isAuthenticated,navigationRef]) This feels a bit weird to use but would actually be safe and eslint would be ok with that. |
Yea, but the logic also has I'm just saying that the current code is incorrect, not if it'll be correct if we gurrantee that these methods are stable. It also helps to stop thinking in terms of
I agree that it can be annoying. But I disagree that it'll create bugs. The rule can cause de-opts, but I can't think of a scenario where it actually creates bugs if the code is correct. Rather, it surfaces existing bugs in the code. Happy to be proven wrong.
I'd not. Because we don't guarantee that this is stable. So should we guarantee that all methods in I'm of the opinion is how a prop is created and stored is parent's implementation detail. Child shouldn't rely on it.
It does complicate the code. But it's your choice and your specific use case whether simpler code is better than correct code. I don't have any general opinion on that. |
Maybe there should be |
I have no idea what to do :) Having |
Hi @satya164 I've been thinking a bit about that and I think:
It should be easier to do the memoization directly in a hook with useRef, and the users most likely to useEffect(fn,[navigate]) are also most likely to obtain the navigate function through a hook, so I think it's fine if only the hooks package offer the memoization. If you think I should not support that contract please tell me. Also feel free to reopen this issue if you think there's something to change in core (you mentioned that maybe getChildrenNavigationCache.js was not working correctly) |
Related to this hooks issue: react-navigation/hooks#3 (comment)
People are trying to use the navigate() function in useEffect, and try to comply to eslint dependency rules, unfortunately the navigation function provided by the NavigationContext changes often, and lead to unwanted effect re-executions on each screen change.
Not sure exactly how to fix this, but it would be nice if the navigation actions would be memoized to avoid being recreated.
Could it make sense to cache per route key the result of
getNavigationActionCreators
/router.getActionCreators
?https://github.com/react-navigation/core/blob/e514a61f9314e8d771231de9d3a4ebf64f661003/src/getNavigation.js#L46
https://github.com/react-navigation/core/blob/e514a61f9314e8d771231de9d3a4ebf64f661003/src/routers/getNavigationActionCreators.js
The text was updated successfully, but these errors were encountered: