Passing an empty array as the second argument to useEffect makes it only run on mount and unmount, thus stopping any infinite loops.
useEffect(() => {
setIngredients({});
}, []);
This was clarified to me in the blog post on React hooks at https://www.robinwieruch.de/react-hooks/
Answer from WhiteFluffy on Stack OverflowPassing an empty array as the second argument to useEffect makes it only run on mount and unmount, thus stopping any infinite loops.
useEffect(() => {
setIngredients({});
}, []);
This was clarified to me in the blog post on React hooks at https://www.robinwieruch.de/react-hooks/
Had the same problem. I don't know why they not mention this in docs. Just want to add a little to Tobias Haugen answer.
To run in every component/parent rerender you need to use:
useEffect(() => {
// don't know where it can be used :/
})
To run anything only one time after component mount(will be rendered once) you need to use:
useEffect(() => {
// do anything only one time if you pass empty array []
// keep in mind, that component will be rendered one time (with default values) before we get here
}, [] )
To run anything one time on component mount and on data/data2 change:
const [data, setData] = useState(false)
const [data2, setData2] = useState('default value for first render')
useEffect(() => {
// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
}, [data, data2] )
How i use it most of the time:
export default function Book({id}) {
const [book, bookSet] = useState(false)
const loadBookFromServer = useCallback(async () => {
let response = await fetch('api/book/' + id)
response = await response.json()
bookSet(response)
}, [id]) // every time id changed, new book will be loaded
useEffect(() => {
loadBookFromServer()
}, [loadBookFromServer]) // useEffect will run once and when id changes
if (!book) return false //first render, when useEffect did't triggered yet we will return false
return <div>{JSON.stringify(book)}</div>
}
useEffect causes an infinite loop?
Infinite loop in useEffect using blank object or array
reactjs - Passing a function in the useEffect dependency array causes infinite loop - Stack Overflow
reactjs - useEffect runs infinite loop despite no change in dependencies - Stack Overflow
Why is the dependency array important in useEffect?
What causes infinite loops in useEffect?
How can I prevent an infinite loop in useEffect?
Hey!! First time building a website and have ran into this issue when trying to fetch content from Sanity Studio.
Here's the link: https://pastebin.com/3iL0gpBt
Copying what I think is the relevant part of the code
export default function IssuesList() {
const [items, setItems] = useState([]);
useEffect(() => {
sanityClient
.fetch(
`*[_type == "issue"] | order(publishedAt desc) {
title,
slug,
description,
frontCover{
asset->{
_id,
url
}
}
}`
)
.then((data) => {
setItems(data);
})
.catch(console.error);
});
return (
<div css={issuesListSx}>
<Frame path={[{ name: "Issues", slug: "/issues" }]}>
<Grid gap={2} columns={[1, null, 2]} className="issuesGrid">
{items.map((issue) => {
return (
<div className="issueItem" key={issue.title}>
<Link to={"/issues/" + issue.slug.current}>
<div>{issue.title}</div>
{issue.frontCover && "asset" in issue.frontCover && (
<img src={issue.frontCover.asset.url} alt="" />
)}
</Link>
</div>
);
})}
</Grid>
</Frame>
</div>
);
}E: Fixed!! Had to add an empty array as a second parameter to useEffect
The issue is that upon each render cycle, markup is redefined. React uses shallow object comparison to determine if a value updated or not. Each render cycle markup has a different reference. You can use useCallback to memoize the function though so the reference is stable. Do you have the react hook rules enabled for your linter? If you did then it would likely flag it, tell you why, and make this suggestion to resolve the reference issue.
const markup = useCallback(
(count) => {
const stringCountCorrection = count + 1;
return (
// Some markup that references the sections prop
);
},
[/* any dependencies the react linter suggests */]
);
// No infinite looping, markup reference is stable/memoized
useEffect(() => {
if (sections.length) {
const sectionsWithMarkup = sections.map((section, index) => markup(index));
setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
} else {
setSectionBlocks([]);
}
}, [sections, markup]);
Alternatively if the markup function is only used in the useEffect hook you can move it directly into the hook callback to remove it as an external dependency for the hook.
Example:
useEffect(() => {
const markup = (count) => {
const stringCountCorrection = count + 1;
return (
// Some markup that references the sections prop
);
};
if (sections.length) {
const sectionsWithMarkup = sections.map((section, index) => markup(index));
setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
} else {
setSectionBlocks([]);
}
}, [sections, /* any other dependencies the react linter suggests */]);
Additionally, if the markup function has absolutely no external dependencies, i.e. it is a pure function, then it could/should be declared outside any React component.
Why is an infinite loop created when I pass a function expression
The "infinite loop" is the component re-rendering over and over because the markup function is a NEW function reference (pointer in memory) each time the component renders and useEffect triggers the re-render because it's a dependency.
The solution is as @drew-reese pointed out, use the useCallback hook to define your markup function.
The way the useEffect dependency array works is by checking for strict (===) equivalency between all of the items in the array from the previous render and the new render. Therefore, putting an array into your useEffect dependency array is extremely hairy because array comparison with === checks equivalency by reference not by contents.
const foo = [1, 2, 3];
const bar = foo;
foo === bar; // true
const foo = [1, 2, 3];
const bar = [1, 2, 3];
foo === bar; // false
Inside of your effect function, when you do setInvoiceData(results) you are updating invoiceData to a new array. Even if all the items inside of that new array are exactly the same, the reference to the new invoiceData array has changed, causing the dependencies of the effect to differ, triggering the function again -- ad infinitum.
One simple solution is to simply remove invoiceData from the dependency array. In this way, the useEffect function basically acts similar to componentDidMount in that it will trigger once and only once when the component first renders.
useEffect(() => {
const updateInvoiceData = async () => {
const results = await api.invoice.findData();
setInvoiceData(results);
};
updateInvoiceData();
}, []);
This pattern is so common (and useful) that it is even mentioned in the official React Hooks API 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.
Credit to jered for the great "under the hood" explanation; I also found Milind's suggestion to separate out the update method from useEffect to be particularly fruitful. My solution, truncated for brevity, is as follows -
const Invoices = () => {
const [invoiceData, setInvoiceData] = useState([]);
useEffect(() => {
updateInvoiceData();
}, []);
// Extracting this method made it accessible for context/prop-drilling
const updateInvoiceData = async () => {
const results = await api.invoice.findData();
setInvoiceData(results);
};
return (
<div>
<OtherComponentThatUpdatesData handleUpdateState={updateInvoiceData} />
<Table entries={invoiceData} />
</div>
);
};