Infinite loop that does not crash but causes 300% CPU - reactjs

I have this code that makes my app hang and Expo using 300% CPU. It looks like an infinite loop but it does not crash and I can't figure out why it's an infinite loop or why its not crashing.
I scraped out the useless parts:
ContainerComponent.js
const [cardsReady, setCardsReady] = useState(0)
const cardLoaded = () => {
setCardsReady(cardsReady + 1)
}
return keys.map((component) => {
<Card key={'some key}
articles={'somearticle'} color={'#eeeeee'}
onLoaded={cardLoaded}
category={'some category}
/>
})
Card.js
useEffect(() => {
onLoaded()
})

please use a dependencies array to only execute useEffect hook once.
Card.js
useEffect(() => {
onLoaded()
},[]);
In the dependencies array you can leave it empty for execute it once as i said or put dependencies variables, that is, specify the variables when it value changes will execute code inside useEffect.

The issue here with your code is with every state update in functional component it is re-rendered.
To avoid that issue you need to add dependency array to useEffect, and we are checking the if the component has mounted or not
Card.js
const Card = (props) => {
useEffect(() => {
let mounted = true;
if(mounted && props.onLoaded){
props.onLoaded();
}
return () => mounted=false;
//eslint-disable-next-line
},[]);
return <div></div>;
};
I have created a local state to check whether the component is loaded or not. I know the answer is a bit complicated

Related

How dose the useEffect and useState hook works?

Hi I am learning React Hooks I know useEffect when used without any dependency array should run after every render but what about render itself does it happen every time a state changes because I am trying following code where I am rending a static h1 to the DOM and in my useEffect I am using a fetch to call a json file on the success of the fetch I am setting a state loaded to true from false I have written this example for the learning purpose only I was expecting that this should have caused a infinite loop because useEffect without dependency array should run after every render and I am calling set state which according to me should force the render to happen but It only run the useEffect twice but
when I am using the other example where I am using useState create a name state and in the useeffect without dependency arry I am changing the name state with Math.random it is behaving as expected causing infinite loop what I understand is useState does not cause render state to happen untill the value of the state changed as in the first example the state value only changed once from false to true and in name example it is random so changes every time
Just need a to know is my understanding correct and also if you can share where I can learn these Kind of stuff as I did not find this on the react doc.
example 1 with fetch
`
const App = () => {
// const [name, setName] = useState('sumit');
const [loaded, setLoaded] =useState(false);
useEffect(() => {
console.log('sumit');
fetch('./option.json').then((res) => {
console.log(res);
return res.json();
}).then((res) => {
console.log(res);
setLoaded(true);
})
// setName(Math.random());
})
return (
<h1>sumit</h1>
);
}
example 2 with name
const App = () => {
const [name, setName] = useState('sumit');
// const [loaded, setLoaded] =useState(false);
useEffect(() => {
console.log('sumit');
/* fetch('./option.json').then((res) => {
console.log(res);
return res.json();
}).then((res) => {
console.log(res);
setLoaded(true);
}) */
// setcount(count+1);
setName(Math.random());
})
return (
<h1>sumit</h1>
);
}
`

Counter in react-js run only once when Component Re-Render?

I tried setting a timer to a function I want to be called every 2 seconds:
// start timer
if(!timerStarted){
tid = setInterval(ReloadMessage, 2000);
timerStarted = true
}
But I want this timer instance to only be ran once (hence the !timerStarted)
Unfortunately, this gets ignored when the component rerenders from the state change.
I tried ending the timer but I found no way to know in advance when the state changes.
So I tried:
//my Functional component useEffect
React.useEffect(()=>{
(async () => {
// start timer
if(!timerStarted){
tid = setInterval(ReloadMessage, 2000);
timerStarted = true
}
})()
},[])
Thinking this would make the effect be called only once upon component load, but this ended up not calling the timer at all (Maybe because I also have a second effect with dependencies here?)
How do I make sure this timer is set off once and only once, no matter what the user does?
Using an empty dependencies array for your effect, will ensure that it only runs once. With that in mind, it's kind of irrelevant to track that a timerStarted.
The usage of this flag (provided it's a variable scoped to the component) even indicates that it actually should be a dependency, which your linter, if you have one, should notify you of. Though as stated above you don't need it, and it would only make things more complicated.
Also the async IIEF is not needed as you don't await anything.
So, all in all, this should be enough:
React.useEffect(()=>{
const tid = setInterval(ReloadMessage, 2000);
return () => {
clearInterval(tid);
};
},[]);
As per the comments, here's a simple demo of how you can use a ref, to get access to some dependency that you absolutely do not want to list as a dependency. Use this sparingly and only with good consideration, because it often hints at a problem that started somewhere else (often a design problem):
import { useEffect, useRef, useState } from 'react';
const Tmp = () => {
const [counter, setCounter] = useState(0);
const counterRef = useRef(counter);
useEffect(() => {
counterRef.current = counter;
}, [counter]);
useEffect(() => {
const t = setInterval(() => {
console.log('Invalid', counter); // always *lags behind* because of *closures* and
// will trigger a linter error, as it should actually be a dependency
console.log('Valid', counterRef.current); // current counter
}, 2000);
return () => {
clearInterval(t);
};
}, []);
return (
<div>
<div>
<button onClick={() => setCounter(current => current - 1)}>-</button>
{counter}
<button onClick={() => setCounter(current => current + 1)}>+</button>
</div>
</div>
);
};
export default Tmp;

I Got Stuck in infinite loop in react.js. How to resolve this?

I Got Stuck in an infinite loop in react.js. How to resolve this?
useEffect(() => {
fetch("https://react-http-d55a9-default-rtdb.firebaseio.com/todo.json")
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
setUsersList((prev) => [...prev]); //cause of infinite loop
});
}, [usersList]);
You are having an infinite loop because your useEffect array of dependencies has usersList on it and at the same time you are updating this variable inside your useEffect function. So your useEffect runs when the component mounts which updates your usersList which makes the useEffect run again which again updates your usersList which makes it run again and so on...
To fix this, remove usersList from the array of dependencies and have an empty array instead: []. If you do this your useEffect will run once, when your component mounts.
The dependency list passed to useEffect determines when the effect should run again. The infinite loop is happening because this effect causes usersList to change, which triggers the effect to run again.
Since this effect doesn't use any other variables, it doesn't need anything in its dependency list:
useEffect(() => {
fetch(...)
// ...
}, []); // leave this empty, so the effect only runs when the component mounts
If your URL depended on a prop or something else, then you want it in the dependency list:
useEffect(() => {
fetch(`https://example.com/todo/${props.id}`)
.then(...)
// Since the URL depends on the id prop, the effect should re-run if it changes
}, [props.id]);
According to question asked, you want the userList to be watched everytime it updates. What we can do is define one more state variable as mentioned in the code as isFetched or if you are using redux you can put that over there, because if we just watch the userList variable then it caughts up in infinite loop as setting the userList is happening in useEffect itself. With the help of isFetched, we can manage when to call the api and whenever the flag is false it calls the api.
Right now in the code i have put one more state variable as setCount, as i didn't know how many times you want to call your api. So you can put your condition there and stop the call when your condition satisfies.
function App() {
const [userList, setUserList] = useState([]);
const [isFetched, setIsFetched] = useState(false);
const [, setCount] = useState(3);
const callApiPending = useCallback(()=>{
fetch("https://react-http-d55a9-default-rtdb.firebaseio.com/todo.json")
.then((response) => response.json())
.then((json) => {
setUserList((prev) => [...prev, ...json]);
setCount((cnt) => {
if(cnt - 1 === 0){
setIsFetched(true);
}
return cnt - 1;
});
});
}, []);
useEffect(() => {
if (!isFetched) {
callApiPending();
}
}, [isFetched, userList, callApiPending]);
return <div>Executing....</div>;
}
You ran fetch if usersList changes. Even if userList content is the same as previous content, javascript interpret as it changed. Try this one.
[1,2,3] == [1,2,3]
may return false. You can use a flag which is used to check whether or not to get data instead of using array.

Infinite re-render in functional react component

I am trying to set the state of a variable "workspace", but when I console log the data I get an infinite loop. I am calling the axios "get" function inside of useEffect(), and console logging outside of this loop, so I don't know what is triggering all the re-renders. I have not found an answer to my specific problem in this question. Here's my code:
function WorkspaceDynamic({ match }) {
const [proposals, setProposals] = useState([{}]);
useEffect(() => {
getItems();
});
const getItems = async () => {
const proposalsList = await axios.get(
"http://localhost:5000/api/proposals"
);
setProposals(proposalsList.data);
};
const [workspace, setWorkspace] = useState({});
function findWorkspace() {
proposals.map((workspace) => {
if (workspace._id === match.params.id) {
setWorkspace(workspace);
}
});
}
Does anyone see what might be causing the re-render? Thanks!
The effect hook runs every render cycle, and one without a dependency array will execute its callback every render cycle. If the effect callback updates state, i.e. proposals, then another render cycle is enqueued, thus creating render looping.
If you want to only run effect once when the component mounts then use an empty dependency array.
useEffect(() => {
getItems();
}, []);
If you want it to only run at certain time, like if the match param updates, then include a dependency in the array.
useEffect(() => {
getItems();
}, [match]);
Your use of useEffect is not correct. If you do not include a dependency array, it gets called every time the component renders. As a result your useEffect is called which causes setProposals then it again causes useEffect to run and so on
try this
useEffect(() => {
getItems();
} , []); // an empty array means it will be called once only
I think it's the following: useEffect should have a second param [] to make sure it's executed only once. that is:
useEffect(() => {
getItems();
}, []);
otherwise setProposal will modify the state which will trigger a re-render, which will call useEffect, which will make the async call, which will setProposal, ...

With useEffect, how can I skip applying an effect upon the initial render?

With React's new Effect Hooks, I can tell React to skip applying an effect if certain values haven't changed between re-renders - Example from React's docs:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
But the example above applies the effect upon initial render, and upon subsequent re-renders where count has changed. How can I tell React to skip the effect on the initial render?
As the guide states,
The Effect Hook, useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
In this example from the guide it's expected that count is 0 only on initial render:
const [count, setCount] = useState(0);
So it will work as componentDidUpdate with additional check:
useEffect(() => {
if (count)
document.title = `You clicked ${count} times`;
}, [count]);
This is basically how custom hook that can be used instead of useEffect may work:
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useEffect(() => {
if (didMountRef.current) {
return fn();
}
didMountRef.current = true;
}, inputs);
}
Credits go to #Tholle for suggesting useRef instead of setState.
Here's a custom hook that just provides a boolean flag to indicate whether the current render is the first render (when the component was mounted). It's about the same as some of the other answers but you can use the flag in a useEffect or the render function or anywhere else in the component you want. Maybe someone can propose a better name.
import { useRef, useEffect } from 'react';
export const useIsMount = () => {
const isMountRef = useRef(true);
useEffect(() => {
isMountRef.current = false;
}, []);
return isMountRef.current;
};
You can use it like:
import React, { useEffect } from 'react';
import { useIsMount } from './useIsMount';
const MyComponent = () => {
const isMount = useIsMount();
useEffect(() => {
if (isMount) {
console.log('First Render');
} else {
console.log('Subsequent Render');
}
});
return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};
And here's a test for it if you're interested:
import { renderHook } from '#testing-library/react-hooks';
import { useIsMount } from '../useIsMount';
describe('useIsMount', () => {
it('should be true on first render and false after', () => {
const { result, rerender } = renderHook(() => useIsMount());
expect(result.current).toEqual(true);
rerender();
expect(result.current).toEqual(false);
rerender();
expect(result.current).toEqual(false);
});
});
Our use case was to hide animated elements if the initial props indicate they should be hidden. On later renders if the props changed, we did want the elements to animate out.
I found a solution that is more simple and has no need to use another hook, but it has drawbacks.
useEffect(() => {
// skip initial render
return () => {
// do something with dependency
}
}, [dependency])
This is just an example that there are others ways of doing it if your case is very simple.
The drawback of doing this is that you can't have a cleanup effect and will only execute when the dependency array changes the second time.
This isn't recommended to use and you should use what the other answers are saying, but I only added this here so people know that there is more than one way of doing this.
Edit:
Just to make it more clear, you shouldn't use this approach to solving the problem in the question (skipping the initial render), this is only for teaching purpose that shows you can do the same thing in different ways.
If you need to skip the initial render, please use the approach on other answers.
I use a regular state variable instead of a ref.
// Initializing didMount as false
const [didMount, setDidMount] = useState(false)
// Setting didMount to true upon mounting
useEffect(() => { setDidMount(true) }, [])
// Now that we have a variable that tells us wether or not the component has
// mounted we can change the behavior of the other effect based on that
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
We can refactor the didMount logic as a custom hook like this.
function useDidMount() {
const [didMount, setDidMount] = useState(false)
useEffect(() => { setDidMount(true) }, [])
return didMount
}
Finally, we can use it in our component like this.
const didMount = useDidMount()
const [count, setCount] = useState(0)
useEffect(() => {
if (didMount) document.title = `You clicked ${count} times`
}, [count])
UPDATE Using useRef hook to avoid the extra rerender (Thanks to #TomEsterez for the suggestion)
This time our custom hook returns a function returning our ref's current value. U can use the ref directly too, but I like this better.
function useDidMount() {
const mountRef = useRef(false);
useEffect(() => { mountRef.current = true }, []);
return () => mountRef.current;
}
Usage
const MyComponent = () => {
const didMount = useDidMount();
useEffect(() => {
if (didMount()) // do something
else // do something else
})
return (
<div>something</div>
);
}
On a side note, I've never had to use this hook and there are probably better ways to handle this which would be more aligned with the React programming model.
Let me introduce to you react-use.
npm install react-use
Wanna run:
only after first render? -------> useUpdateEffect
only once? -------> useEffectOnce
check is it first mount? -------> useFirstMountState
Want to run effect with deep compare, shallow compare or throttle? and much more here.
Don't want to install a library? Check the code & copy. (maybe a star for the good folks there too)
Best thing is one less thing for you to maintain.
A TypeScript and CRA friendly hook, replace it with useEffect, this hook works like useEffect but won't be triggered while the first render happens.
import * as React from 'react'
export const useLazyEffect:typeof React.useEffect = (cb, dep) => {
const initializeRef = React.useRef<boolean>(false)
React.useEffect((...args) => {
if (initializeRef.current) {
cb(...args)
} else {
initializeRef.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dep)
}
Here is my implementation based on Estus Flask's answer written in Typescript. It also supports cleanup callback.
import { DependencyList, EffectCallback, useEffect, useRef } from 'react';
export function useDidUpdateEffect(
effect: EffectCallback,
deps?: DependencyList
) {
// a flag to check if the component did mount (first render's passed)
// it's unrelated to the rendering process so we don't useState here
const didMountRef = useRef(false);
// effect callback runs when the dependency array changes, it also runs
// after the component mounted for the first time.
useEffect(() => {
// if so, mark the component as mounted and skip the first effect call
if (!didMountRef.current) {
didMountRef.current = true;
} else {
// subsequent useEffect callback invocations will execute the effect as normal
return effect();
}
}, deps);
}
Live Demo
The live demo below demonstrates the different between useEffect and useDidUpdateEffect hooks
I was going to comment on the currently accepted answer, but ran out of space!
Firstly, it's important to move away from thinking in terms of lifecycle events when using functional components. Think in terms of prop/state changes. I had a similar situation where I only wanted a particular useEffect function to fire when a particular prop (parentValue in my case) changes from its initial state. So, I created a ref that was based on its initial value:
const parentValueRef = useRef(parentValue);
and then included the following at the start of the useEffect fn:
if (parentValue === parentValueRef.current) return;
parentValueRef.current = parentValue;
(Basically, don't run the effect if parentValue hasn't changed. Update the ref if it has changed, ready for the next check, and continue to run the effect)
So, although other solutions suggested will solve the particular use-case you've provided, it will help in the long run to change how you think in relation to functional components.
Think of them as primarily rendering a component based on some props.
If you genuinely need some local state, then useState will provide that, but don't assume your problem will be solved by storing local state.
If you have some code that will alter your props during a render, this 'side-effect' needs to be wrapped in a useEffect, but the purpose of this is to have a clean render that isn't affected by something changing as it's rendering. The useEffect hook will be run after the render has completed and, as you've pointed out, it's run with every render - unless the second parameter is used to supply a list of props/states to identify what changed items will cause it to be run subsequent times.
Good luck on your journey to Functional Components / Hooks! Sometimes it's necessary to unlearn something to get to grips with a new way of doing things :)
This is an excellent primer: https://overreacted.io/a-complete-guide-to-useeffect/
Below solution is similar to above, just a little cleaner way i prefer.
const [isMount, setIsMount] = useState(true);
useEffect(()=>{
if(isMount){
setIsMount(false);
return;
}
//Do anything here for 2nd render onwards
}, [args])
You can use custom hook to run use effect after mount.
const useEffectAfterMount = (cb, dependencies) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Here is the typescript version:
const useEffectAfterMount = (cb: EffectCallback, dependencies: DependencyList | undefined) => {
const mounted = useRef(true);
useEffect(() => {
if (!mounted.current) {
return cb();
}
mounted.current = false;
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
};
Example:
useEffectAfterMount(() => {
document.title = `You clicked ${count} times`;
}, [count])

Resources