We can use the useRef hook to store any mutable value we like, so we could use that to keep track of if it's the first time the useEffect function is being run.
If we want the effect to run in the same phase that componentDidUpdate does, we can use useLayoutEffect instead.
Example
const { useState, useRef, useLayoutEffect } = React;
function ComponentDidUpdateFunction() {
const [count, setCount] = useState(0);
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<ComponentDidUpdateFunction />,
document.getElementById("app")
);
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
Answer from Tholle on Stack OverflowWe can use the useRef hook to store any mutable value we like, so we could use that to keep track of if it's the first time the useEffect function is being run.
If we want the effect to run in the same phase that componentDidUpdate does, we can use useLayoutEffect instead.
Example
const { useState, useRef, useLayoutEffect } = React;
function ComponentDidUpdateFunction() {
const [count, setCount] = useState(0);
const firstUpdate = useRef(true);
useLayoutEffect(() => {
if (firstUpdate.current) {
firstUpdate.current = false;
return;
}
console.log("componentDidUpdateFunction");
});
return (
<div>
<p>componentDidUpdateFunction: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<ComponentDidUpdateFunction />,
document.getElementById("app")
);
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
You can turn it into custom hook (new documentation page: Reusing Logic with Custom Hooks), like so:
import React, { useEffect, useRef } from 'react';
const useDidMountEffect = (func, deps) => {
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) func();
else didMount.current = true;
}, deps);
}
export default useDidMountEffect;
Usage example:
import React, { useState, useEffect } from 'react';
import useDidMountEffect from '../path/to/useDidMountEffect';
const MyComponent = (props) => {
const [state, setState] = useState({
key: false
});
useEffect(() => {
// you know what is this, don't you?
}, []);
useDidMountEffect(() => {
// react please run me if 'key' changes, but not on initial render
}, [state.key]);
return (
<div>
...
</div>
);
}
// ...
Title. Basically i have a useEffect with a specific state variable in the dependency array. I want the side effect to be fired only when it does actually change, not in first render. I tried with a simple useRef guard, but with the new changes to useEffect(fires twice) it doesn't work. Removing the strictmode makes it work, should i just ignore it since when i switch to production it should work?
useEffect fires at initial render
reactjs - I want to know why my useEffect function is not running on the first render? - Stack Overflow
Is there a way to NOT fire a useEffect hook on initial render?
How to make a component render only AFTER a useEffect hook?
Videos
Effects don't run during a render.
The first execution of an effect function will be triggered by the first render.
useEffect is called after the first render however the issue in your code is that the words state isn't updated when you think it is.
useEffect(() => {
setWords(sentence.split(' '));
// Here you set the words state that will trigger a new render. However the words variable is empty won't be changed until the next render
words.map(word =>{ //Here you call map on an empty array.
if(word.length <= 2){
scrambledWord.push(word);
}
else{
scrambledWord.push(scrambleWord(word));
}
})
setScrambledSentence(scrambledWord.join(' '));
//Here you set the state on an empty string
}, [])
// the useEffect doesn't run a second time because it has no dependencies.
This code should work:
const ScrambleSentence = ({sentence}) => {
const [scrambledSentence, setScrambledSentence] = useState('');
function scrambleWord(n){
var text = n.split('');
for(var i = text.length - 1; i > 0; i--){
var j = Math.floor(Math.random() * (i + 1));
var temp = text[i];
text[i] = text[j];
text [j] = temp;
}
return text.join('');
}
useEffect(() => {
let scrambledWord = [];
const words = sentence.split(' ')
words.forEach(word =>{
if(word.length <= 2){
scrambledWord.push(word);
}else{
scrambledWord.push(scrambleWord(word));
}
})
setScrambledSentence(scrambledWord.join(' '));
}, [])
console.log(scrambledSentence);
return (
<div>
<p id='scrambled-word'>{scrambledSentence}</p>
</div>
)
};
export default ScrambleSentence;
https://reactjs.org/docs/hooks-state.html
https://reactjs.org/docs/hooks-effect.html
There are two issues at play here:
You are using Ref, which when updates will not trigger a re-render, which means once your REST call returns, the component will not re-render with your new data. Try using state instead of ref.
useEffect does run on the first render, but it is not blocking the render. Meaning, on the first render, useEffect is triggered, but it does not wait for the REST call to return before rendering the component. There are several methods to deal with it:
- you can return null of there are no items in the array
- you can use optional chaining (as the answer above suggests)
- you can display a loading screen if there are no items in the array
and many more...
You are combining two things:
- When useEffect is Run
- Receiving a Response from the server
To actually test out whether useEffect ran, you can:
- Add a console.log before that request and see if it got logged
- Inspect your Network Tab to see if a Request to the Endpoint was triggered
Since your UI is dependent upon the response, it will be a good thing to add a:
- Loader
- Fallback component in case the response received is not valid
useEffect is supposed to run on every render unless a dependency array is provided, which will ensure that it only runs if a dependency changes. By providing an empty array, you are limiting useEffect to run on JUST the first render.
This is a bit of a weird question, maybe, but I have a use case for a custom hook based on useEffect to return data from an API based on user input. I have it mostly working however I have noticed that my hook is running on initial render and making an unnecessary API request. Obviously useEffect does this by default, but I was wondering if there was a way to avoid this.
I tried conditionally calling the hook, but React considers that an error and refuses to compile. I also tried conditionally returning within the hook, but same issue. Lastly I tried calling the hook with useCallback, but that still ran on initial render, as well.
My current code is available here: https://codesandbox.io/s/exciting-hooks-1psw6
Any thoughts?
I have a component with a useEffect to fetch some data and it conditionally renders different children based on the response. So something like this
function Component() {
const [names, setNames] = useState([])
useEffect(() => {
getNames().then(data => names = data)
}, [names])
return (
<div>
{names.length ? <Success /> <Error />}
</div>
)Upon first render, it shows the Error component, and right after a split second, it shows the Success component. I want it to only render once the useEffect is finished. Is there anyway to do that?