index.js:1 Warning: State updates from the useState() and useReducer()
Hooks don't support the second callback argument. To execute a side
effect after rendering, declare it in the component body with
useEffect().
This is the error I get when trying to do this
setUnansweredQuestions({ newUnansweredQuestions }, () =>
nextQuestion()
);
I tried run a function after updating the state for unanswered questions but won't work cause it doesn't update right away.
I searched a bit and it is said to use useEffect but I already have one defined and won't let me create another. I just want to call the function nextQuestion after updating UnansweredQuestions
useEffect(() => {
setUnansweredQuestions(questions);
selectRandomQuestion();
}, [currentQuestion]);
There's nothing wrong with having multiple useEffects.
Since you do setUnansweredQuestions and want to run something after that state variable changes, just do:
useEffect(nextQuestion, unansweredQuestions);
You can try like this, I know this is not a good solution, but it works
setState(UnansweredQuestions);
setTimeOut(() => {
nextQuestion()
}, 16)
Related
Why am I getting this error?
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.**
This is my code:
const [roles, setRoles] = useState([]);
useLayoutEffect(() => {
setRoles(["5nxg5wvb"]);
});
Note that the same error appears when I use useEffect, and that error only goes away when I change the code to this:
useLayoutEffect(() => {
setRoles("5nxg5wvb");
});
Any help would be appreciated...
You need to provide a dependency array in setEffect or it will run on every state change. Since it's changing state, that means it will run, which causes it to run again, which causes it to run again, etc...
The dependency array tells it when to run. An empty array means "run this effect on mount," so it will run only once. An array with variable names in it means, "run when any of these variables change."
useLayoutEffect(() => {
setRoles("5nxg5wvb");
}, []);
Why did this error occur?
Because you are using useEffect or useLayoutEffect while you don't specify the second params of the hooks, you only put the first param which is the callback function, and the state value is always different.
Why does putting this ["5nxg5wvb"] doesn't work while putting "5nxg5wvb" works?
Because if you use [] (an array), you create a new object and compare it with the previous object every time it re-renders (because you don't specify the second params of the hooks, but do know that [] === [] will result in false value)
While if you use "" (a string), React will directly compare the value instead, and if it's the same, the state won't be updated and there will be no re-rendering.
How to fix this?
You can put the second param of the hooks like this, the code below will make the hooks run only once:
useLayoutEffect(() => {
setRoles(["5nxg5wvb"]);
}, []);
Just put the new value directly to the useState like this, there is no need to put useEffect with static data:
const [roles, setRoles] = useState(["5nxg5wvb"]);
If both suggestions don't work for your use case, please do put more info about your use case.
I was going through useEffect from reactjs docs and I've found this statement here
Experienced JavaScript developers might notice that the function
passed to useEffect is going to be different on every render.
We are passing a function to useEffect and this function is said to be different for each render. useEffect has access to state and props since it's inside the function component and when either of these changes, we can see that change in the function of useEffect(because of closure) right? This is not clear, because in the next line the doc states
This is intentional. In fact, this is what lets us read the count
value from inside the effect without worrying about it getting stale.
To counter this, assume we have a function
function foo(n) {
bar = () => {
setTimeout(() => console.log({n}), 50);
return n;
}
setTimeout(() => {n = 10}, 0);
setTimeout(() => {n = 20}, 100);
setTimeout(() => {n = 30}, 150);
return bar;
}
baz = foo(1);
baz(); //prints 10
setTimeout(baz, 300); //prints 30
It seems that when the closure value(n) is changed, we can see that change in the setTimeout's callback (and this callback isn't changed over time). So, how can the closured value(state/props) in useEffect's function become stale as mentioned in docs?
Am I missing something here? I think it's more of a JS question compared to React, so I took a JS example.
I found the answer a few days back, and as #apokryfos(Thank you again!) mentioned in the comments above, the program execution process is now making more sense. I want to summarize my learnings here.
Firstly, the code I considered, was not like with like comparison (in #apokryfos words) with the React doc statements, and this is true. In case of static HTML + vanilla JS where the HTML button has an event-listener JS function, this function is declared only once and when the event occurs the same JS function is executed everytime.
The code I have given in the question is similar to this, and so when executed in console or in event listener will only be declared once.
In case of React(or any state based UI libraries/frameworks), the HTML is not static and it needs to change on state-change. On the execution side (considering React), component will be created when we call it (in JSX), and the component's function/class will be executed completely from top to bottom. This means
from all the event-handlers that doesn't deal with state, constants to useState's destructed elements and useEffect's callback functions, everything are re-initialized.
If the parent's state changes initiate a render on its children(in normal scenarios), then the children will need to re-render themselves completely with the new props and/or new state to show the updated UI
Considering the example in React docs (link), useEffect with no dependencies will get executed after every render, and here it's updating the DOM by showing the current state value. Unless the callback function has the latest value of that state, it'll only print the stale value. So re-initialising the functions here is the main reason behind not having stale values in the callback functions
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = 'You clicked ${count} times';
});
}
This is a boon and a curse sometimes. Assume if we are sending useState's setState function to the child, and in the child, we have a useEffect(that makes a network call to fetch data) that takes this function as its dependency. Now, for every state change in parent, even if there is a use-case or not, the useEffect will get triggered as this function in dependency is changed (as every update re-initializes the functions). To avoid this, we can utilize useCallback on the functions which we want to memorize and change only when certain params are changed, but it is not advisable to use this on useEffect's callback function since we might end-up in stale values.
Further Reading:
GeeksForGeeks useCallback
SourceCode interpretation of useEffect
Another SourceCode interpretation of useEffect
I have a screen with some choices on. If you select the choice it sets state of the data. I then have a confirm button. if the user hits confirm I make an async call to get some extra data. I want to wait for this to happen before opening the modal as I need to present that extra data in my modal.
before hooks I would use setState and do something like:
this.setState({data: myData}, () => this.openModal()) as this would reliably set the state then open the modal. all the answers online seem to suggest using useEffect but it seems dodgy to do this:
useEffect(() => {
if (data) {
setModalOpen(true)
}
}, [data, setData])
I don't want my modal potentially randomly opening at different points. plus it seems better to have the code living in the same place I set state. it makes sense to be there. not some random useEffect
any suggestions how this can be achieved?
(one other solution I can think of is making the API call on every choice select, rather than before confirm) however, this could lead to a lot of unnecessary API calls so I'd rather not go down that route.
Using useEffect() is correct, I also encountered this issue when trying to do a callback on setState with hooks.
Like you said: this.setState({data: myData}, () => this.openModal()) was possible before, but now when trying this with hooks the console displays the error:
Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
So useEffect() seems the way to go.
You should use useEffect() as a callback after the state is correctly setted if you would like to do something with the state like validation.
useEffect(() => {
// fetch on load
axios.get("https://randomuser.me/api/").then((response) => {
setPerson(response.data.results[0]);
});
}, []);
useEffect(() => {
// do some validation perhaps
if (person !== null) {
if (person.name.first && person.name.last) {
setModal(true);
} else {
setModal(false);
}
}
}, [person]); // add person in dependency list
As suggested in the comments, you could also do setModal() when the async data has arrived (using .then() or await).
Some example code using random user generator API and axios for fetching.
useEffect(() => {
// fetch on load
axios.get("https://randomuser.me/api/").then((response) => {
setPerson(response.data.results[0]);
setModal(true); // set modal visibility
});
}, []);
setState updates state asynchronously. It's my understanding that, when using a class component, you can do something like this to ensure certain code is executed after one setState updates state:
setState({color: red}, callbackThatExecutesAfterStateIsChanged);
I'm using a functional component & hooks. I'm aware, here, useEffect()'s callback will execute everytime after color state changes and on initial execution.
useEffect(callback, [color]);
How can I replicate similar behaviour as the class component example - that is, to execute a chunk of code once after one setState() successfully changes state and not on initial execution?
If you ask me, there is no safe way to do this with hooks.
The problem is that you both have to read and set an initialized state in order to ignore the first update:
const takeFirstUpdate = (callback, deps) => {
const [initialized, setInitialized] = useState(false);
const [wasTriggered, setWasTriggered] = useState(false);
useEffect(() => {
if (!initialized) {
setInitialized(true);
return;
}
if (wasTriggered) {
return;
}
callback();
setWasTriggered(true);
}, [initialized, wasTriggered]);
};
While the hook looks like it works, it will trigger itself again by calling setInitialized(true) in the beginning, thus also triggering the callback.
You could remove the initialized value from the deps array and the hook would work for now - however this would cause an exhaustive-deps linting error. The hook might break in the future as it is not an "official" usage of the hooks api, e.g. with updates on the concurrent rendering feature that the React team is working on.
The solution below feels hacky. If there's no better alternative, I'm tempted to refactor my component into a class component to make use of the easy way class components allow you to execute code once state has been updated.
Anyway, my current solution is:
The useRef(arg) hook returns an object who's .current property is set to the value of arg. This object persists throughout the React lifecycle. (Docs). With this, we can record how many times the useEffect's callback has executed and use this info to stop code inside the callback from executing on initial execution and for a second time. For example:
initialExecution = useRef(true);
[color, setColor] = useState("red");
useEffect(() => {
setColor("blue");
});
useEffect(() => {
if (initialExecution.current) {
initialExecution.current = false;
return;
}
//code that executes when color is updated.
}, [color]);
Why does the load() function with [] as the second argument not run on mount unless I pass in [load] to the second argument? I do not want to pass in the second argument as I only want the load() function to run once on mount and never afterward. How do I achieve this?
const HoursTimer = (props) => {
const [isActive, setIsActive] = useState(null);
const [activeId, setActiveId] = useState(null);
useEffect(() => {
load();
}, [])
function load() {
props.hours.forEach((hour, i) => {
if (i === (props.hours.length-1)) {
setIsActive(hour.clockedIn)
setActiveId(hour._id)
}
})
}
return (
<div>
<p>{isActive}</p>
<p>{activeId}</p>
</div>
);
};
function mapStateToProps( { hours } ) {
return { hours };
}
export default connect(mapStateToProps, { fetchHours })(HoursTimer);
I've never used React before, but a quick review of the useEffect source, and I think I have and answer for you.
useEffect takes two arguments, as you know. But did you know the array, aka "deps" tells React, when you want to rerun the effect? React does this by "rechecking" those deps.
So a short hand for run once, but never again is an empty array.
I confirmed this from React's documentation
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
If you pass an empty array ([]), the props and state inside the effect
will always have their initial values. While passing [] as the second
argument is closer to the familiar componentDidMount and
componentWillUnmount mental model, there are usually better solutions
to avoid re-running effects too often. Also, don’t forget that React
defers running useEffect until after the browser has painted, so doing
extra work is less of a problem.
Another insightful comment from the documentation
The array of dependencies is not passed as arguments to the effect
function. Conceptually, though, that’s what they represent: every
value referenced inside the effect function should also appear in the
dependencies array. In the future, a sufficiently advanced compiler
could create this array automatically.
So depending on you desired behavior, if you want to run the effect, everytime the prop.hours updated you might need to do something like
useEffect(() => {
load();
}, [props.hours])
function load() {
props.hours.forEach((hour, i) => {
if (i === (props.hours.length-1)) {
setIsActive(hour.clockedIn)
setActiveId(hour._id)
}
})
}
Looking into things a bit more, I found this article, which may be helpful.
As for you question:
Why does the load() function with [] as the second argument not run on
mount unless I pass in [load]
I suspect, it's because [load] is a function, which react, will rerun, but
it's doing so to check if it needs to rerun the effect... with the side effect, of well, having already, rerun load, which just so happens to be your entire useEffect. -- that was at least 3 levels of inception.
Hope that helps.