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.
Related
I have a custom hook that is used by N components across the app at the same time.
Inside the custom hook there is a useEffect which executes at the right time / correctly given the dependencies however it executes N number of times (with each component where it is loaded)... which seems right.
I was wondering nevertheless if there would be a way to make it execute only once even though it's "parent" custom hook is "mounted" multiple times.
Thanks
let alreadyCalled = false;
function myHook() {
useEffect(() => {
if (alreadyCalled) {
return;
}
alreadyCalled = true;
//calculate here
})
}
The point of useEffect is to bind the execution of a piece of logic to the lifecycle of the component(s) where it is used. You're asking how to not tie that logic to the lifecycle of certain components.
It sounds like it should not be used in any of those N components, but perhaps in a parent component which would then pass the resulting state down via props or context.
If it's buried in a custom hook, you might need to pull it out of there, call it in parent component, and pass whatever state it produces as an argument to that custom hook.
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.
I'm trying to do an ajax call when a state changes, and then set another state to the result of that ajax call.
const [plc, setPlc] = useState(null);
const [tags, setTags] = useState([]);
...
useEffect(()=>{
if(plc != null) {
myAjaxPromiseFunction(plc).catch((err)=>{
console.log(err);
}).then((tags)=>{
setTags(tags);
});
}
}, [plc]);
For some reason, this results in an infinite loop. However, when I remove the setTags(tags); statement it works as expected. It appears that the setTags function is causing the effect hook to update, but tags is not a part of the dependencies of this effect hook.
FYI, the tags variable is supposed to be a list of objects.
EDIT:
I have one other useEffect in my code.
useEffect(() => { // Update which tags are logged
if(plc != null) {
anotherAjaxFunction(plc, tags).catch(err => {
toaster.show({
message: err,
intent: "danger"
});
}).then(tags => {
setTags(tags);
});
}
}, [plc, updateLoggedFlag]);
However, this useEffect is not dependant on tags, and the issue still occurs if I remove this block of code.
Side note: The updateLoggedFlag is a variable I was planning to use to force the effect to update as needed, but I haven't used it anywhere yet.
Another EDIT: Link to reproduction using code sandbox: https://codesandbox.io/s/cranky-ives-3xyrq?file=/src/App.js
Any ideas? Thanks for the help.
In short: move TagColumn outside of App, as it is here.
The problem is not connected to the effects declaration, it's due to the component declaration.
In your sandbox, the TagColumn was defined inside of App. It means that every time App was rendered (in particular, every time its state is changed), the const TagColumn was assigned a different value. React can't see that it is the same component every time - after all, it's not really the same, since the captured scope is changing.
So, App renders a new TagColumn. React sees that component is created from scratch, renders it, and, in particular, invokes its hooks, including every useEffect. Inside useEffect, you use callback to change the state of App. App rerenders, creates yet another component TagColumn, which is again rendered for the first time in its life, calling every useEffect... and the cycle continues.
In general, you shouldn't capture anything non-constant in the functional component scope. Instead:
if the value is generated by external context (App, in this case) - pass it as props;
if the value is generated by the component itself - use useState, useRef or other hooks, as necessary.
The problem I think with your code is that it does not include tags in the dependency array. Conceptually, this should work fine, as mentioned before by other developers, but React documentation states that, all the state variables and props used inside the callback, should be present inside the dependency array, otherwise it can cause bugs. Have a look at it here. Hooks FAQ
https://codesandbox.io/s/cocky-silence-486sh?file=/src/App.js
In the above example, we see that calling the ChildWrapper as ChildWrapper() vs <ChildWrapper/> causes some differences.
The function method behaves as intended, so when I increment the counter, the reference stays the same, however using the component call, it actually remounts.
I was wondering if my intuition is correct, that the references are changing, and more important why are they changing. And to add on to that, which should be the preferred method for rendering functions that returns jsx, <Component/> or { Component() }.
When you do <ChildWrapper/> it is translated to React.createElement(ChildWrapper, {}, null) which means you ask react to create an element for you.
When you do ChildWrapper() you are just calling a function, react does not know about this and cannot know about it, so you won't have any of react's features (hooks).
With your example, with logging the two results in the console you will see this, and you can notice that the difference is noticeable:
When calling the function, it will return a <div>, here, jsx will transpiles it to an object with type div.
When calling it via jsx from the start, it will return an object with type ChildWrapper.
PS: you are creating the ChildWrapper inside a component (during render) So it will have a new value each time your Parent component renders, and react after rendering and going into reconcialiation, it will remove the whole previous tree and create a new one, because the type changed.
Let's sum up all what I've wrote:
The child "behaving correctly for you" is the function call, because you basically call it and it returns a div, react after render and during reconciliation, finds a div and then just updates it if there is a change. So the mount useEffect(() => ..., []) will not be executed.
Why the child created with jsx isn't behaving correclty ? because you are dynamically creating its type, so at each render, it will create a new ChildWrapper Type, and react during reconciliation will remove it entirely because its type changed, and thus, the mount useEffect(() => ..., []) will be executed each time, because every time we mount a new element.
Read more about reconciliation in this link
From the official documentation :
Each JSX element is just syntactic sugar for calling
React.createElement(component, props, ...children). So, anything you
can do with JSX can also be done with just plain JavaScript.
Internally, the code generated looks like this: React.createElement(Component, { props }, children),
The createElement function will then register and render the component you gave to it, bind it with the shadow DOM and update it whenever it is necessary. It will also pass its props and children arguments.
But if you call your component directly, none of that happens, your component will not be correctly rendered and updated by react because it lacks the core features given by createElement.
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');
});