The lint error is there for a reason and ignoring it might cause bugs that are very hard to find.
No dependency array
If you exclude the dependency array completely (useEffect(() => { /* ... */ });) the effect will run on every render, causing an infinite loop.
Empty dependency array
If you add an empty dependency array (useEffect(() => { /* ... */ }, [])) the effect will only run on first render. That might work here but it will cause issues in other scenarios, or if something change in the future. E.g. say that request takes some time to initialize (so it's first undefined, to later change to an object) and that initializeAccount looks like below. Now initalizeAccount would run only on first render with request === undefined, when request is ready in a later stage and the component is re-rendered, initalizeAccount won't run again and the functionality will silently fail.
const initializeAccount = async () => {
if (request) {
const user = await request.get('api/user/')
if (user) {
setUser(user.data)
}
}
}
Populated dependency array (the right way)
If you add initializeAccount to the dependency array (useEffect(() => { /*...*/ }, [initalizeAccount])) the effect will run every time initializeAccount is changed. For objects (and functions) React compares the reference from the last render with the reference from the current render to determine if the object has changed. The way you define initializeAccount it will be redefined on every render and hence the reference will be new and the effect will run on every render, just as you experience.
What you need to do is to obtain a stable reference to initalizeAccount. For this React provides useCallback, it is similar to useEffect in that it has a dependency array. If we define initializeAccount with useCallback, React will make sure that the reference is the same between renders. It is only when one of the dependencies in the dependency array of useCallback change that the function is redefined which in turn triggers the useEffect to run again. This way you are adhering to the lint rule while at the same time the effect is only run once.
const initializeAccount = useCallback(async () => {
if (request) {
const user = await request.get('api/user/')
if (user) {
setUser(user.data)
}
}
}, [request])
Note that if request, in turn, doesn't have a stable reference, useCallback will be run on each render (as described above) and then so will useEffect. If useFetch is written by you, make sure it returns an object with a stable reference. If useFetch is provided from a library, that is most likely the case already.
TL;DR
Wrap initializeAccount in useCallback and add it to the dependency array of useEffect.
const initializeAccount = useCallback(async () => {
try {
const user = await request.get('api/user/')
const profile = await request.get('api/profile/')
const posts = await request.get('api/posts/')
if (user) {
setUser(user.data)
}
if (profile) {
setProfile(profile.data)
}
if (posts) {
setPosts(posts.data)
}
} catch (e) {
console.log('could not initialize account')
}
}, [request])
useEffect(() => {
initializeAccount()
return () => console.log('unmount')
}, [initializeAccount])
const initializeAccount = useCallback(async () => {,,,},[request]);And passinitializeAccountto the useEffect.