Why setState(hook) in function component cause an infinite loop? - javascript

Here is the code:
import React, { useState } from 'react'
function App() {
const [a, setA] = useState(1)
setA(2)
return (
<div>
<h1>{a}</h1>
</div>
);
}
Error. Too many re-renders. React limits the number of renders to prevent an infinite loop.
Why it can cause an infinite loop?
I think the reason is that function component just as a render function, so it will cause infinite loop when setState in render functions.
Is there any official explanation?

On each state update React will re-render the component and run all the function body, as the setA(2) is not enclosed in any hook or function and is a part of the function/component body. React will execute this on each render cycle. This make an infinite loop.
On Component mount React will set the state and go for the component update as the state updated, again there is state update React will again re-render the component. This cycle will continue until react reaches the re-render limit.
You could avoid this by wrap the state update in hook.
import React, { useState } from 'react'
function App() {
const [a, setA] = useState(1)
useEffect(() => {
setA(2)
},[]);
return (
<div>
<h1>{a}</h1>
</div>
);
}

When calling setA you actually update a state variable and trigger a re-render of you component.
When the component re-render it will call setA (just before the rendering) and will trigger a re-render again.
Can you see the infinite loop ?
Traditionally you update a state variable into a callback (i.e when the user click on a button) or under certains conditions.
In your example, you can directly set a to 2
function App() {
const [a, setA] = useState(2)
return (
<div>
<h1>{a}</h1>
</div>
);
}
If you want to have the first render with a = 1, then immediatly have a = 2, you can use an effect which will be executed only once (because of the empty array for the second argument)
function App() {
const [a, setA] = useState(2)
useEffect(() => setA(2), [])
return (
<div>
<h1>{a}</h1>
</div>
);
}

Because you are setting a new value to the state with setA(2), so each time the component is rendered the state receives a new value, forcing the component to render again

const [rows, setRows] = useState([]);
return {
<Component data= {someData}
selectCallback ={ (rows) => {
console.log(rows);
setRows(rows); // if set State going infinity loop
}}
...remainProps
/>
Its giving infinity loop when trying to setState and if we didn't use the setRows then in console giving right answers means array of objec
Help appreciated.

Because calling setA(2) causes the component to re-render.

Related

Component should return same value wherever it's called. and Main Component should not rerender when Sub Component useState is updating in react?

I want same value in Home function from Component and Home function should not rerender when Component useState is updating.
import { useState, useEffect } from "react";
function Component() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log("rerender")
})
useEffect(() => {
let interval = setInterval(() => {
setCount(Math.floor(Math.random() * 1000))
}, 1000)
return () => clearInterval(interval)
}, [])
return count
}
function OtherStuff() {
console.log("OtherStuff")
return (<h1>Should Not Rerender OtherStuff</h1>)
}
function MoreStuff() {
console.log("MoreStuff")
return (<h1>Should Not Rerender MoreStuff</h1>)
}
export default function Home() {
return (
<>
<h1>Hello</h1>
<h1>
<Component />
</h1>
<OtherStuff />
<MoreStuff />
<h1>
<Component />
</h1>
</>
);
}
I have called Component function two times, I want same value should render to both from Component.
and when ever Component {count} is update by using setCount, Main function should not rerender. at this time, there is no rerender for Component in Main function which is OK.
this is the simple problem.
Thanks.
Each instance of a component has its own state. Meaning their states are not shared. If you wanna share the state between them, one way would be to lift the state up.
If you do not want the Home to rerender, you cannot move the state to Home. So you probably need another component which holds the state. Because any component which has a react useState hook, will be rerendered when the state is updated.
You can use react context to do so. Wrap your instances of Component inside a context provider which holds the count state and provides count and setCount.
First you need to create a context:
const CountContext = createContext();
Then create a component which provides the context and holds the state:
const CountProvider = (props) => {
// `useCounter` uses `useState` inside.
const [count, setCount] = useCounter();
return (
<CountContext.Provider value={{ count, setCount }}>
{props.children}
</CountContext.Provider>
);
};
(OP provided a codesandbox which uses useCounter custom hook. It stores a number to count state and updates it to a random number every 1000ms.)
Then wrap it around your components:
function Parent() {
return (
<div>
<CountProvider>
<Counter />
<Counter />
</CountProvider>
</div>
);
}
codesandbox
Note that:
If you render another component inside CountProvider using children prop, it will NOT rerender every time count state updates, unless it uses the context.
But if you render a component inside CountProvider and render it in the function, it WILL rerender, unless you use memo.

React component re-rendering many times

i have a react component thats keep re-rendering idk why but i think the reason is the data fetching
data code :
export function KPI_Stock_Utilisation() {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
setKpi_stock_utilisation((existingData) => {
return response.data;
});
});
}, []);
console.log('data get')
return kpi_stock_utilisation;
}
this log displayed many times , and the log in the component too
component code :
import React from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import { useEffect } from "react";
export default function WarehouseUtilisChart(props) {
let kpi_stock_utilisations =KPI_Stock_Utilisation();
let Stock_utilisation = (kpi_stock_utilisations.length / 402) * 100;
console.log('component render')
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
im new with react i tried useEffect inside the componenets but its not working
Calling the react custom hook KPI_Stock_Utilisation several times will for sure render more than once.
in your case I suggest you use useEffect in the same component as I will show you.
import React,{useEffect,useRef} from "react";
import { KPI_Stock_Utilisation } from "../../Data/data";
import axios from 'axios';
export default function WarehouseUtilisChart(props) {
const [kpi_stock_utilisation, setKpi_stock_utilisation] = useState([{}]);
const stock_utilisation= useRef(0);
useEffect(() => {
axios.get("http://localhost:5137/KPI_Stock_Utilisation").then((response) => {
stock_utilisation.current = (response.data.length / 402) * 100;
setKpi_stock_utilisation(response.data);
});
//this will guarantee that the api will be called only once
}, []);
//you should see this twice, one with the value 0, and another one, the calculated data
console.log('component render',stock_utilisation.current)
return (
<div>
<p>{kpi_stock_utilisations}</p>
</div>
);
}
To note, if you call this component from more than one location, for sure it will render several times - keep that in mind.
On the other hand, all your variables should always start with a lower case and try to name your variables like this: instead of kpi_stock_utilisation change it to kpiStockUtilisation for a better coding practice
You got into infinite loop.
Its hard to explain why it doesn't work as expected, but I can try.
First of all, useEffect with empty array of dependencies works like componentDidMount and fires only after (!) first render.
So you have some value returned from your let kpi_stock_utilisations =KPI_Stock_Utilisation(); then it rendered, after this your useEffect fires a request and set state, setting of state trigger re-render and new value to return, this new value trigger your parent component to return let kpi_stock_utilisations =KPI_Stock_Utilisation(); might run again.
If you are trying to create a custom hook for fetching some info, follow rules of hooks
I hope it helped you

React hook setState causing infinite renders

I wonder why this gets stuck in infinite renders as soon as I set it's value coming from backend API.
I don't want to stop it using useEffect() as I need to use it to re render after the values have been assigned
CODE :
import React, { useState, useEffect } from 'react';
// import FormCheckLabel from 'react-bootstrap/FormCheckLabel';
import './CheckForms.css';
// import Scrollspy from '../Scrollspy/Scrollspy';
import axios from 'axios';
const CheckForms = () => {
const [menu, setMenu] = useState([]);
const fetchList = async () => {
try {
const res = await axios.get(`http://localhost:5000/api/items`);
const list = res.data.response;
// setMenu(list); // <-- This is causing ulimited renders ! //
} catch (err) {
console.log(err.response);
};
};
fetchList();
console.log("something");
return (
<div>
</div>
)
}
export default CheckForms;
I would really appreciate the help. Thanks.
you cannot place fetchList(); outside useEffect() because,
when component is mounted, fetchList() is called, it sets state and component rerenders.
again fetchList() is executed and again sets state and comoenent again rerenders. it forms an infinite loop.
you must do
useEffect(()=>fetchList(),[])
or execute it on an event
you need useEffect for this. Otherwise fetchList will be called on each re render.
So your infinite loop comes from :
fetchList() => setMenu() => render() => fetchList() => setMenu() and so on ...
I don't want to stop it using useEffect() as I need to use it to re
render after the values have been assigned
useEffect(callback, [triggers]) second parameter is here exactly for that, control when a re render should happen, based on props/state changes

Updating component's state using props with functional components

I am trying to update the state of my component using props that I get from the parent component, but I get the following error message:
Too many re-renders. React limits the number of renders to prevent an infinite loop.
I want the local state to update if the prop changes.
The similar posts (Updating component's state using props, Updating state with props on React child component, Updating component's state using props) did not fixed it for me.
import React, {useState} from "react"
const HomeWorld = (props) => {
const [planetData, setPlanetData] = useState([]);
if(props.Selected === true){
setPlanetData(props.Planet)
console.log(planetData)
}
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
export default HomeWorld
You need to use the useEffect hook to run it only once.
import { useEffect } from 'react'
...
const HomeWorld = (props) => {
const [planetData, setPlanetData] = useState([]);
useEffect(() => {
if(props.Selected === true){
setPlanetData(props.Planet)
console.log(planetData)
}
}, [props.Selected, props.Planet, setPlanetData]) // This will only run when one of those variables change
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
Please notice that if props.Selected or props.Planet change, it will re run the effect.
Why Do I Get This Error ?
Too many re-renders. React limits the number of renders to prevent an infinite loop.
What is happening here is that when your component renders, it runs everything in the function, calling setPlanetData wich will rerender the component, calling everything inside the function again (setPlanetData again) and making a infinite loop.
You're generally better off not updating your state with props. It generally makes the component hard to reason about and can often lead to unexpected states and stale data. Instead, I would consider something like:
const HomeWorld = (props) => {
const planetData = props.Selected
? props.Planet
//... what to display when its not selected, perhaps:
: props.PreviousPlanet
return(
<h1>hi i am your starship, type: {planetData}</h1>
)
}
This might require a bit more logic in the parent component, to control what displays when the Selected prop is false, but it's a lot more idiomatic React.

How to use useEffect hook properly with array dependency. I passed state from redux store and still my component renders infinitely

I am using useEffect hook and getting a list of users data with fetch call using function getStoreUsers which dispatches an action on response and stores shopUsers(which is an array) inside the redux store.
In array dependency, I am writing [shopUsers]. I don't know why it is causing infinite rendering.
Here is how I am using useEffect hook:
useEffect(() => {
const { getStoreUsers, shopUsers } = props;
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch(() => {
setLoading(false);
});
}, [shopUsers]);
I want to re-render component only when data inside shopUsers array changes.
If I write shopUsers.length inside array dependency. It stops to re-render.
But, let's suppose I have have a page which opens up when the user clicks on a userList and updates user data on next page. After the update I want the user to go back to the same component which is not unmounted previously. So, In this case array length remains the same, but data inside in of array index is updated. So shopUsers.length won't work in that case.
You can make a custom hook to do what you want:
In this example, we replace the last element in the array, and see the output in the console.
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { isEqual } from "lodash";
const usePrevious = value => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
const App = () => {
const [arr, setArr] = useState([2, 4, 5]);
const prevArr = usePrevious(arr);
useEffect(() => {
if (!isEqual(arr, prevArr)) {
console.log(`array changed from ${prevArr} to ${arr}`);
}
}, [prevArr]);
const change = () => {
const temp = [...arr];
temp.pop();
temp.push(6);
setArr(temp);
};
return (
<button onClick={change}>change last array element</button>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Live example here.
Your effect is triggered based on the "shopUsers" prop, which itself triggers a redux action that updates the "shopUsers" prop and thats why it keeps infinitely firing.
I think what you want to optimize is the rendering of your component itself, since you're already using redux, I'm assuming your props/state are immutable, so you can use React.memo to re-render your component only when one of its props change.
Also you should define your state/props variable outside of your hooks since they're used in the scope of the entire function like so.
In your case, if you pass an empty array as a second param to memo, then it will only fire on ComponentDidMount, if you pass null/undefined or dont pass anything, it will be fired on ComponentDidMount + ComponentDidUpdate, if you want to optimise it that even when props change/component updates the hook doesn't fire unless a specific variable changes then you can add some variable as your second argument
React.memo(function(props){
const [isLoading, setLoading] = useState(false);
const { getStoreUsers, shopUsers } = props;
useEffect(() => {
setLoading(true);
getStoreUsers().then(() => {
setLoading(false);
}).catch((err) => {
setLoading(false);
});
}, []);
...
})

Categories

Resources