What is causing Error: Too many re-renders in this code? - reactjs

Can anybody please look at the following ReactJS component and tell what is causing it to return the error:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
import React, { useState, useEffect } from 'react';
function Lab() {
const [questions, setQuestions] = useState([]);
const addQuestion = (question) => {
let q = [...questions];
q.push(question);
setQuestions(q);
}
addQuestion('What is your name?');
addQuestion('Where do you belong?');
return (
<div>
{
questions.map( q => <div>{q}</div>)
}
</div>
);
}
export default Lab;
Edit:
I can use some default values in useState([]), but that would make my code much messy because the data structure is quite complicated. That's why I want to push default values from within a helper function. Isn't it possible this way?

PROBLEM
Lab function is executed.
addQuestion is executed which triggers a re-render
Re-render triggers another execution of addQuestion
and thereby causes an infinite loop of re-renders and execution of addQuestion.
SOLUTION
add your default question as the default state in your Lab component.
import React, { useState, useEffect } from 'react';
const defaultQuestions = [
'What is your name?', 'What is your name?'
]
function Lab() {
const [questions, setQuestions] = useState(defaultQuestions);
const addQuestion = (question) => {
let q = [...questions];
q.push(question);
setQuestions(q);
}
return (
<div>
{
questions.map( q => <div>{q}</div>)
}
</div>
);
}
export default Lab;

Whenever the state update, the whole function component will re-run again.
Therefore, in your code, when the questions variable is updated, the execution of the addQuestion function will be called again, and the function itself will update the state again, and that causes the infinite loop.
To prevent this kind of situation, it's better to let an event trigger the function.
Edit
If adding some default values is your main purpose, this is how you can do it:
import React, { useState, useEffect, useRef } from 'react';
function Lab() {
const isDefaultValueLoaded = useRef(false);
const [questions, setQuestions] = useState([]);
const addQuestion = (question) => {
let q = [...questions];
q.push(question);
setQuestions(q);
}
// To set the default values:
// use an useEffect hook to load the values when the component just mount.
useEffect(() => {
// to prevent the infinite loop,
// use a ref object as a flag to make sure the function will only run once.
if (isDefaultValueLoaded.current === false) {
loadDefaultValue();
isDefaultValueLoaded.current = true;
}
}, []);
// by writing the code above, you can now seperate the logic into an "helper function" as you mentioned.
const loadDefaultValue = () => {
addQuestion('What is your name?');
addQuestion('Where do you belong?');
}
return (
<div>
{
questions.map(q => <div>{q}</div>)
}
</div>
);
}
export default Lab;

Related

Why is my useState variable initialized every time my React components is rendered?

While working on some custom hooks in React I have observed the following behavior: Every time my component renders, my useState variables are initialized. More specifically, I have observed this by using an auxiliary function to dynamically create data for testing.
In order to demonstrate this behavior of React, I have created a super simple "TestComponent" (please refer to the code below). Every time my component renders, it logs "testData called!" in the console.
What did I do wrong? How can I prevent React from calling my "test-data-generating-function" testData every time my component renders?
PS: I tried to wrap my function testData into a useCallback hook. But that does not work since useCallback cannot be used at "top level". Also, instead of finding a "workaround", I would really like to understand the root cause of my problem.
import React, { useEffect, useState } from 'react';
const testData = (): number[] => {
console.log('testData called!');
const returnValues: number[] = [];
for (let i = 1; i <= 10; i++) {
returnValues.push(i);
}
return returnValues;
};
const TestComponent = () => {
// useState hooks
const [data, setData] = useState<number[]>(testData());
const [counter, setCounter] = useState<number>(1);
// useEffect hook
useEffect(() => {
const increaseCounter = () => {
setCounter((counter) => counter + 1);
setTimeout(() => increaseCounter(), 1000);
};
increaseCounter();
}, []);
return (
<div>
{data.map((item) => (
<p key={item}>{item}</p>
))}
<p>{counter}</p>
</div>
);
};
export default TestComponent;
I believe what happens here is that your function to retrieve the data (testData()) is being called for each render, but not necessarily updates the state.
A react component is essentialy a function, and for each render the whole function is executed (including the call to testData()).
If you use the callback signature of useState, it will be called only on the first render:
const [data, setData] = useState(() => testData());

Incorrect use of react hooks but no hook errors/faults

React hooks can only be used inside
Functional components
Other hooks
If you don't follow the above rules, react complains about not following the rules via a fault
But, the below code works (albeit with lint errors) without any stack-trace/errors
import React from 'react'
import { useState, createContext, useContext } from "react";
const SomethingContext = createContext("what is the thing");
// Correct usage
const useSomething = () => {
const something = useContext(SomethingContext);
return something;
};
//Incorrect usage
const getTheThing = () => {
const theThing = useContext(SomethingContext); // works with useState as well (ex below)
return theThing;
};
//Incorect usage
const getTheThingState = () => {
const [theThing, setTheThing] = useState("I am the state");
return theThing;
};
const Child = () => {
const theThing = getTheThing(); //OR getTheThingState()
return `The thing is "${theThing}"`;
};
export default function App() {
return (
<div className="App">
<SomethingContext.Provider value={"it is something"}>
<Child />
</SomethingContext.Provider>
</div>
);
}
Here's a demo of the above code. The linter points out the incorrect usage, but the odd part where I'm confused is that React itself doesn't throw any error.
Not able to figure out WHY?
All resources point out to fix the above problem by changing the function into a component i.e function's variable name (I'm aware of it)
const GetTheThing = () => {
//...
But, when does react decide to say "okay, you are not supposed to do that" vs "okay, I'll let this one slide".
Technically there is no problem with the code provided. As the invocation of useContext and useState happening inside component's code. And there is no issue of having one outer function, which just calls the hook inside. Yes, we are breaking the Only Call Hooks at the Top Level rule. But if you go through rationale behind this rule - you'll see that the ordering of invocations is the key here. And having function without any nested if statements still guarantees the ordering. So your code:
export const useSomething = () => {
const something = useContext(SomethingContext);
return something;
};
const getTheThing = () => {
const theThing = useSomething();
return theThing;
};
const Child = () => {
const theThing = getTheThing();
return `The thing is "${theThing}"`;
};
Still complies with correct ordering of:
Render fn of Child component started
useContext invoked
Render fn of Child component finished

useContext returns undefined when my redux store updates

I am having trouble understanding why my useContext value updates from the expected values to undefined when the redux store is updated. I am using create context to house my socket connection for my discussions. The following is my context class
import React, { createContext, useRef } from 'react'
export const WebSocketContext = createContext()
export default ({ children }) => {
const webSocket = useRef(null);
let ws;
const openedDiscussionID = useSelector(state => state.presence.discussionId || '')
const sendMessages = (messageObject) => {
webSocket.current.send(JSON.stringify(messageObject))
}
if (!webSocket.current){
webSocket.current = new WebSocket(``)
webSocket.current.onmessage = (message) => {
const discussionMessage = JSON.parse(message.data)
};
ws = {
webSocket: webSocket.current,
sendMessages
}
}
return(
<WebSocketContext.Provider value={ws}>
{children}
</WebSocketContext.Provider>
)
}
I am currently using it in my parent component called projectDetailContainer,
import WebSocketProvider from '../../redux-state/middleware/socketService'
function ProjectDetailContainer() {
return (
<WebSocketProvider>
<div>parent component</div>
</WebSocketProvider>
)
}
export default ProjectDetailContainer
somewhere down the chain, I have the following discussion component:
import { WebSocketContext } from '../../redux-state/middleware/socketService'
function DiscussionOptionContainer() {
const ws = useContext(WebSocketContext)
...
useEffect(() => {
if(openedDiscussion.title?.length){
dispatch(setOpenDiscussion(openedDiscussion))
}
},[openedDiscussion])
const sendMessage = () => {
const mes = {
action:'discussion',
message:{
customerId:'7240304',
projectId:projectId,
message:message,
itemId:openedDiscussion.discussionId,
sender: me
}
}
debugger
ws.sendMessages(mes)
}
...}
The thing I noticed was that as soon as the app loads, I see the sendMessages function being returned but as soon as the dispatch occurs, it changes the ws (useContext) to undefined. If I comment out the part that updates the redux store, it works as expected. My theory is that the redux store provider is higher in the chain than my WebSocketProvider but i cant confirm.
useSelector triggers a rerender when selected value changes. ws is undefined by default and gets a value if webSocket.current is falsy. And it seems to happen only on the initial render. Every consecutive render will set ws to undefined and pass it to WebSocketContext.Provider.
Try moving ws = {.....} outside condition.

UseEffect doesn't behave as expected -

When I refresh the page useEffect only render one function out of the two inside, and when I change routes it works normally.
I am using redux to set global state with the vehicles and dispatching the API in useEffect
so I have vehicles available all the time. however, the second function vchStatusNumbers that it should return the length of the array filtered as per its status , it only runs once, and when i add its state as dependency i get an infinite loop!
I need to understand how i should approach it?
Below is the component
import React,{useEffect,useState} from "react";
import StatisticBanner from "./StatisticBanner";
import {getAllVehicles,fetchVehiclesReport } from "./vehiclesReducer";
import { useSelector, useDispatch } from "react-redux";
const Home= ()=> {
const {vehicles} = useSelector(getAllVehicles); // get the state
const [statusTotal, setStatusTotal] = useState({})
const dispatch = useDispatch(); // dispatch fn to reducers
useEffect(() => {
dispatch(fetchVehiclesReport());
vchStatusNumbers();
}, [dispatch]);
const vchStatusNumbers = () =>{
const status = {}
let availableLength = 0
let parkedLength = 0
let serviceLength = 0
vehicles.map(vch=>{
if(vch.status === 'available'){
++availableLength
status.available = availableLength
}
if(vch.status === 'parked'){
++parkedLength
status.parked = parkedLength
}
if(vch.status === 'service'){
++serviceLength
status.service = serviceLength
}
})
setStatusTotal (status)
}
return (
<>
<div style={{ margin: 20 }}>
<StatisticBanner key={"statics"} statusTotal={statusTotal} />
</div>
</>
);
}
export default Home
Yes, if you add a dependency to an useEffect hook that ultimately updates that dependency value then this will cause render looping.
Seems vchStatusNumbers should be in a separate useEffect hook with a dependency on the vehicles redux state value.
useEffect(() => {
vchStatusNumbers();
}, [vehicles]);
This is because it is derived state from the vehicles data and won't be updated yet in the first effect that dispatches the action to update it.

React context provider not setting value on page load/refresh

I have the following hook for Pusher and I use it to share one instance across the application.
import React, { useContext, useEffect, useRef } from "react";
import Pusher from "pusher-js";
const PusherContext = React.createContext<Pusher | undefined>(undefined);
export const usePusher = () => useContext(PusherContext)
export const PusherProvider: React.FC = (props) => {
const pusherRef = useRef<Pusher>();
useEffect(() => {
pusherRef.current = new Pusher(PUSHER_APP_KEY, {
cluster: 'eu'
})
return () => pusherRef.current?.disconnect()
}, [pusherRef]);
return (
<PusherContext.Provider value={pusherRef.current}>
{props.children}
</PusherContext.Provider>
)
}
The problem is that the provider always has an undefined value on page refresh/load. But when I trigger a re-render the value is correctly set. I would like to have the instance without the need of re-rendering.
Why is this happening?
I believe you can use the next construction:
export const PusherProvider = (props) => {
const pusher = useMemo(() => new Pusher(APP_PUSHER_KEY, { cluster: 'eu' }), [])
useEffect(() => () => pusher.disconnect(), [pusher])
return <PusherContext.Provider value={pusher}>{props.children}</PusherContext.Provider>
}
I have solved this issue by following way.
If you set State/const data inside "useEffect" will not work. as that will not run when page refresh but the state declaration those are outside the "useEffect" will run. Hence it will reset default values.
So I resolved by setting the state/const value outside of "useEffect" and done.

Resources