How many lifecycle methods can be replaced by React useEffect?
I found an article on the useEffect hook that claims that it can replace only three, i.e. componentDidMount, componentDidUpdate and componentWillUnmount.
What about others?
Nope that's pretty much it, though it comes close to componentWillUpdate when you return a function and dont pass dependencies (it just doesn't run before the first render)
It's a really simple hook when you think about. The rules are pretty straightforward:
The function you pass to useEffect:
runs after every render, unless you provide a dependency array.
If you provide in a dependency array, it runs on first render, and then whenever the dependencies change.
The function your return from your useEffect function:
Is run before each render (except the very first) unless you provide a dependency array.
If you provide a dependency array, it is run before a render where the dependencies have changed, or the component unmounts.
It's probably best not to think about lifecycle events it replaces. Instead think about these 4 rules and how get them to run your functions when you want.
That is actually all. useEffect() is called once component is mounted and than on every state update. If you want to use it as componentWillUnmout you have to return a cleanup function like so
useEffect(() => {
//something you want to do
return () => console.log('cleanup is running');
});
Related
It seems to me that one could replace useEffect/componentDidMount with a state hook and function components. For example, if I wanted to make a fetch in useEffect or componentDidMount, I could simply create a function component that rendered the component that needed fetching, add the fetching method in the function (which will execute upon rendering) which modifies a state hook (so that once the data arrives, the page will re-render with the data). Since React has selective rendering, any other part of the function component that gets updated won't cause an unnecessary fetch.
Am I correct in saying this? Are there any other specific instances where useEffect/componentDidMount is strictly better?
Am not sure why you want to replace componentDidMount in class component or useEffect with a custom function for the use case you highlighted but those two are different in terms of behavior, componentDidMount is one functionality that useEffect provides in function component. useEffect is a combination of componentDidMount and componentWillUnmount and you can have mulitple useEffect on one function component. useEffect accept array of values that could make it run again, all you do is specify those value for that particular useCase you wanted and whenever that values changes the useEffect is called, for UI changes you attach your useState to your logic and if followed accordingly you wont get unnecessary fetch request, if you want useEffect to be called just once then attach just an empty array like this useEffect(()=>{//your fetch logic here },[])
My functional component is structured as follows:
// Initialization
useEffect(() => {
fetchDetailedUserInfo();
}, []);
// Update on initialization of detailedUserInfo
useEffect(() => {
if (detailedUserInfo) {
//...
setInitialFormValues(..);
}
}, [detailedUserInfo]);
// Update on initialization of initialFormValues
useEffect(() => {
//...
}, [initialFormValues]);
// Return JSX
return ( .. );
What determines when the render (return) runs in between all these useEffects, and how are the useEffects ordered among themselves? What is the exact flow?
According to docs:
By default, effects run after every completed render, but you can choose to fire them only when certain values have changed.
The clean-up function runs before the component is removed from the UI to prevent memory leaks. Additionally, if a component renders multiple times (as they typically do), the previous effect is cleaned up before executing the next effect. In our example, this means a new subscription is created on every update. To avoid firing an effect on every update, refer to the next section.
A lot of people tend to miss the above statement on the documentation.
There are two behaviours to be noticed.
When there is an empty dependency array []
In this case the cleanup effect will only get called when the component is going to unmount
When there is some dependency in the dep array
In this case, the cleanup effect is called everytime the useEffect is triggered due to the update phase, it will be called first, then the callback function will be called, so as to ensure that any cleanup is done before the next callback is called.
The main reason for this confusion is that when someone comes from class components this is not the case, and they tend to follow the lifecycle diagram provided.
The hooks LC diagram is not really official, and has a small flaw
https://github.com/Wavez/react-hooks-lifecycle/issues/5
I had raised an issue on the github repo as well with the correction.
Its still open though.
Do checkout the docs here
https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
why dont you do something like this instead?
useEffect(() => {
// you can return the promise inside the fetchDetailedUserInfo function, and ensure that you return the reponse. make sure to add a catch block or handle that in the response.
fetchDetailedUserInfo()
.then(res=>{
setInitialFormValues(...)
})
}, []);
The first useEffect runs when the component is first rendered, so will fire first. The second useEffect will fire whenever detailedUserInfo has changed its value (assuming its not null/false, etc.). The third useEffect will fire whenever initialFormValues has changed its value.
In the following code from reactjs.org:
useEffect(() => {
function tick() {
// Read latest props at any time
console.log(latestProps.current);
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []); // This effect never re-runs
as my brain compiles it, the effect created is a "wait one second then do something", but the setInterval being async itself, it returns immediately then useEffect return its closure callback. React being aware of states changes only and not of actions (it doesn't know what was launched in the useEffect, isn't it? How could he know!), I suppose that it'd fire the closure callback directly on return and then prevent the tick() function to be fired even ounce... but it's not the case. How comes ? How React knows what to wait before firing the closure callback returned by useEffect?
While I don't know exactly what happens in the background, for implementation sake you need only to know that the return callback of a useEffect is only called when the effect is re-ran, or more specifically, after "closing" the previous effect-run and before the new effect running. Depending on the effect's dependencies, it can be on every render, or (as in the example you posted) only when the component is unmounted.
It's useful to think that functional components are just functions, so unless the function (the component) is called again (a re-render or other lifecycle change), the effect is "stopped", there's no magical parallel process. I would risk saying that react checks the hooked effects on a component in pre and post render. Depending on dependencies and the effect's details, it knows whether it should call the return callback or not call the effect at all.
See this sandbox I created where I demo these two most extreme cases: effect on every render, and effect only on mount/unmount. Check the sandbox console to understand the behavior. Try to change the parent's effect dependencies to [count] and see the differences.
PS: when I started using hooks, this article helped me a lot https://overreacted.io/a-complete-guide-to-useeffect/
I am trying to wrap a non-react library and not succeeding.
After the javascript has been loaded, it has an init method where you name a DIV by ID so it attached to it, then later you can call destroy to remove it. I only want the init to be called when the component is loaded and destroy when it is unloaded, and I need access to make method calls on this library.
I have tried withEffect but it seems to get called every time state is set. That is where I had attempted to put the init call.
useEffect(() => {
Library.init(...);
return function cleanup() {
Library.destroy();
}
}
What is the best practice for wrapping a library like this?
useEffect runs by default every time the component renders.
If this is undesirable we can add a dependency array and the effect will only run when at least one of the dependencies has changed.
useEffect(()=>{...}, [])
By making it an empty dependency array, the effect will only run once when the component has mounted. The cleanup will also run only once when the component unmounts.
I am working on incorporating a 3rd-party library into a React app, and hooks are making this very easy overall.
However I have encountered a few issues, and I was hoping for some clarity on what is going on 'under the hood'.
For simplicity, let's say this is my 3rd-party code that mutates the DOM directly:
const renderStuff = (div, txt) => {
if(div) div.innerHTML = txt;
}
And my component is something like this:
export const EffectRender = () => {
const divRef = useRef();
useRenderer(divRef, "Hello, world");
return <div ref={divRef}></div>;
}
Here is the proposed custom hook:
const useRenderer = (ref, txt) => {
const div = ref.current;
useEffect(() => {
renderStuff(div, txt);
},[div, txt])
};
This works if one of the params (in this case, txt) is late-updated, say as a result of an async load.
But the useEffect never recognizes when the ref.current value changes.
So if the txt is set before ref.current is set (as in this case) the component never renders.
I understand that I can fix that by using setState in the custom hook, as in this example.
But this starts to feel cumbersome.
I also understand that I could put the renderStuff call in a useEffect hook on the main component, and that guarantees the ref.current is set.
So: useEffect(() => { renderStuff(divRef.current, txt); },[txt]); is fine.
My question really is whether this whole approach of using a ref inside a custom hook is a good idea.
Is there a simpler way of getting the hook to recognize when the ref has changed?
Or is this a case where custom hooks are not suited to the task?
The problem is that const div = ref.current; in useRenderer is declared outside of the hook. In this moment of the cycle the ref is still not assigned, so its value is null.
If I understood the issue correctly, then the solution is to simply move the ref inside the useEffect callback. This is one of your proposals and I believe its the correct way:
const useRenderer = (ref, txt) => {
useEffect(() => {
const div = ref.current;
renderStuff(div, txt);
}, [txt]);
};
useEffect dependencies will not trigger when the ref the changes. This is ok in your case as the ref already has its assigned value when useEffect runs. However, if the ref changed and you needed to track the change the way to go is using a useState.
Thank you Alvaro for clarifying the issue. I want to explain where my thinking went wrong here, assuming I'm not the only one to make this mistake.
My bad logic
We use effect hooks because the code is mutating the DOM, and useEffect exists to handle exactly this sort of side-effect.
We use ref hooks to connect to a given DOM element. The ref instance is like a singleton, in that the instance doesn't change during the life of the app. Only the ref.current property changes.
And directly under the relevant section in the hooks reference docs I read this:
Keep in mind that useRef doesn’t notify you when its content changes.
Mutating the .current property doesn’t cause a re-render.
From that, I understood that relevant dependencies need to be passed to the useEffect call. And that the element to be updated (in ref.current) was one of these dependencies. And since changing the ref.current property doesn't trigger a re-render, it presumably wasn't triggering the useEffect call either.
BTW: this thinking was reinforced by es-lint demanding I add ref.current (in my case, div = ref.current) into the dependencies list: React Hook useEffect has a missing dependency: 'div'. Either include it or remove the dependency array. I always trust the linter, of course.
My conclusion: there was no simple way to use an effect hook to render to a ref instance. I needed to put the ref.current into a useState setter somehow. Ugly!
My core assumption
I assumed that the way the useEffect handles side-effects is not relevant. The effect hook is a black-box buried in the bowels of the typescript source code, and it moves in mysterious ways.
But the only thing 'buried' was this sentence in the use-effect docs:
The function passed to useEffect will run after the render is
committed to the screen.
"After the render". Of course. That is how useEffect handles side-effects, by guaranteeing not to run until the DOM is ready to go.
The solution
As Alvaro's answer suggests, make sure to read the ref.current property inside the useEffect hook. The ref never changes, and ref.current is guaranteed to be already populated with a DOM element.
As usual, it is obvious in hindsight. Thanks again, Alvaro.