Replies: 9 comments 53 replies
-
I actually think that the "strict effects" thing is going to be a much bigger breaking change in the ecosystem than Concurrency: const didLogRef = useRef(false);
useEffect(() => {
// In this case, whether we are mounting or remounting,
// we use a ref so that we only log an impression once.
if (didLogRef.current === false) {
didLogRef.current = true;
SomeTrackingAPI.logImpression();
}
}, []); We've all been trained for the last two years that if I'm reading these pages right:
So, I foresee a lot of people getting confused at "why did my API call run twice?" here. I almost see a need for an official Also, as a side note, this initial code snippet seems odd: const getterRef = useRef(() => {
if (ref.current === null) {
ref.current = new SomeImperativeThing();
}
return ref.current;
}); I was pretty sure that I think the hooks docs show doing that lazy init while rendering specifically, as the rare edge cases it's legal to mutate while rendering: https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily I assume that's the intended usage pattern here rather than a callback. edit Actually, maybe I misread what the "lazy init function" is here. Is it supposed to be something that's manually called elsewhere? I think I can sorta see how that pattern would work, just not something I'm used to seeing. |
Beta Was this translation helpful? Give feedback.
-
Thanks @bvaughn for documenting these common patterns for improving effects. I love seeing these use cases enumerated with increasing complexity. I'm imagining landing on a doc like this while debugging some strict effects errors and it feels very approachable. |
Beta Was this translation helpful? Give feedback.
-
So, if I'm reading this right effects might get fired and cleaned up multiple times but I feel like it would be useful to have an effect hook that triggered when the |
Beta Was this translation helpful? Give feedback.
-
Example with
I wonder - shouldn't those patterns be provided by React itself? A lot of this complexity~ comes from the React model of hooks, render, and closures, which becomes a specific kind of programming that is not commonly seen in other fields/frameworks etc. I expect that this kind of thing makes the learning curve harder, both for newcomers and even for experienced developers. And if people just copy-paste those things into their projects they might easily lose the context behind those lines of codes after a while. I feel like this also makes it harder to quickly scan the code because more in-depth analysis is required to recognize something as being XYZ pattern and additional time should be spent to validate if that's "just" that or maybe if some additional code has been added to it. |
Beta Was this translation helpful? Give feedback.
-
Is there already work ongoing to think about new eslint-rules to help developers guide away from these "broken" practices or is this a bit too soon? as I read it correctly, don't do ref updates inside a render, always do them in a useEffect or ref function? As we could probably detect 90% of these use-cases pretty easily. |
Beta Was this translation helpful? Give feedback.
-
original comment moved to its own question: |
Beta Was this translation helpful? Give feedback.
-
"auto focus" and subsequent restore focusWe have a modal component that, when closed, returns focus to the element that had focus before focus moved into the modal. Since we have arbitrary children i.e. we don't know which element will receive focus (when the modal is opened) we capture the element that should have its focus restored on close via Minimal implementationfunction Modal({ children }) {
const restoreFocus = React.useRef(null);
function handleFocus(event) {
if (restoreFocus.current === null) {
restoreFocus.current = event.relatedTarget;
}
}
React.useEffect(() => {
return () => {
if (restoreFocus.current !== null) {
restoreFocus.current.focus();
restoreFocus.current = null;
}
};
}, []);
return <div onFocus={handleFocus}>{children}</div>;
} This works in React 17. However, it only works in React 18 with Strict Effects when focus moved into the modal in an effect. When focus moves into the modal via I've created a codesandbox that illustrates the problem space with a naive implementation for There may be other attributes that trigger side-effects suffering from similar problems. |
Beta Was this translation helpful? Give feedback.
-
I didn't think about this for a while since I'm not using this project on a daily basis nowadays. But once upon a time, I was actively using Redux-Saga. When hooks were released we got requests from people to expose some kind of I think though that the idea itself was quite neat and it resurfaced in my mind recently because a new maintainer has volunteered to help with the project. I don't think implementing this is now quite possible though (or at least not without serious drawbacks. The problem is that Redux Saga is heavily-based on generators (but the same problem, even worse, applies to async functions) - those are inherently stateful and can't easily be restarted/put in the same state. Generators are actually quite neat in the sense that they put the calling "runtime" in charge of the execution so we could store all resolved values in memory and "reexecute" them later in a pure fashion to put the generator in the same "state" (in an event sourcing fashion, sort of) but, let's be honest, this isn't a good solution as it potentially has some serious memory implications. So the only other solution that can be used is to force the user to include the "rehydration" logic in the saga's code - that has scalability and readability problems though. Is this my real-world problem? Not quite. But I don't think that it's entirely unreasonable to discard this as a thing that some people would like to use. This is just one of the examples for which the "reusable state" becomes a problem and is limiting, potentially putting some constraints on the users because this particular thing just can't be abstracted away and hidden in a library. Do I have a good example of when this could actually be useful? I think I do: function EmbeddableCheckout() {
const [state, dispatch] = useSaga(checkoutProcess)
// ...
} The checkout process can have multiple steps, trying to tame concurrency problems, race conditions, and all. Perhaps one could make an argument that such a checkout process should support "reusable state" from the beginning because one can refresh a page and ideally they should get back to the same state. It's not always possible to handle all of the things perfectly though - either because there is not enough time to do so or because there are some technical limitations around that. In a lot of situation the "reusable state" might also come from the backend and that initial request that checks the state might be part of the checkout process itself. It's a different problem than being able to reuse "reusable state" after the application already gets past that initial state of the process. |
Beta Was this translation helpful? Give feedback.
-
i write this example This solution is adapted to the new behavior of strict mode and react fast refresh. The solution is based on the fact that after the unmount simulation, a mount simulation will be performed, with the effects running again. Effects that have started more than once after unmounting will be launched. Every unmount, the effects trigger counter is reset to zero. Effects are identified by the name that is passed in the effect method. |
Beta Was this translation helpful? Give feedback.
-
Note: We wrote a new page documenting this behavior in detail on the Beta website: https://beta.reactjs.org/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development.
Hope this is helpful. Sorry some links from there are 404 because other pages are still being written.
Overview
If you have not yet read the previous post about changes to
StrictMode
, you can find it here.Let’s start by taking a look at an example Component:
This component declares some effects to be run on mount and unmount. Normally these effects would only be run once (after the component is initially mounted) and the cleanup functions would be run once (after the component is unmounted). In React 18 Strict Mode, the following will happen:
As long as an effect cleans up after itself (by returning a cleanup method when necessary) this should generally not cause problems. Most effects have at least one dependency. Because of this they are probably already resilient to running more than once and may not require any changes.
Effects that only run on mount will likely require changes though. At a high level, the types of effects that will mostly likely require some modification fall into two buckets:
Effects that require cleanup should have symmetry.
Whether you’re adding event listeners or interacting with some imperative API– as a general rule, if an effect returns a cleanup function then it should mirror the setup function. Many components today use a variation of the pattern shown below.
If the code above gets unmounted and remounted, the imperative thing will likely be broken. (After all, it was destroyed after the first unmount.) To fix this, we need to (re)initialize the imperative thing on mount.
Sometimes other functions (like event handlers) also need to interact with the imperative thing. In that case, a ref can be used to share the value.
Although not as common, the imperative API may also need to be shared with other components. In that case, a lazy initialization function can be used to expose the API.
If you’re unsure about which of the above patterns to use for a particular component, ask us and we’ll help.
Effects that should only run once can use a ref.
Effects that do not require cleanup– even mount effects– might not require any changes to work with the new semantics. Let’s look at an effect that logs impressions to a server.
This effect is meant to log that a user has seen a particular piece of content. What should happen if the content is hidden and then shown again? Should it log a second impression? (That is what the effect would do today if switching tabs remounted the view.) Usually that's the behavior you want, so you don't need to change anything in the code above.
In the rare case where hiding and showing content again doesn't count as a new impression, we could use a ref.
However, usually that is not needed.
The examples above don’t cover every case.
This note covers some of the most common high-level patterns but it’s not an exhaustive list. We plan to write about less common cases in the future. In the meanwhile, if you’re unsure of whether your effect should run more than once– or if your effect doesn’t match one of the patterns shown above– ask us and we’ll help.
Related posts
For more information on the changes to React 18 StrictMode, see:
Beta Was this translation helpful? Give feedback.
All reactions