"Maximum update depth exceeded" in React functional component - reactjs

I have a problem with a react functional component. When the react-apollo query is "on completed" executes a code to handle a token, and when finish send to another page (using history.push).
When run the code I get a infinite loop with this message: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops
I've tried comment history.push and the infinite loop does not happen again.
I comment all the logic in list-session component to avoid a recursive call, but it does not work (I checked thoroughly the code to confirm is not a recursive call).
I thing the problem is related with the lifecycle or with the history.push behavior
Ptt: I'm learning react on this moment
const { setUserInfo, setUserId } = useContext(userContext);
const { token } = match.params;
const onCompleted = data => {
if (data.validateUser.status) {
setUserId(token);
setUserInfo(data.validateUser.infoUser);
localStorage.setItem("token", token);
// history.push("/list-session");
} else {
history.push("/rare-page");
}
};
return (
<div className="Auth" data-testid="AuthPage">
<Query
query={VALIDATE_USER}
variables={{ userId: token }}
onCompleted={onCompleted}
>
{({ error }) => {
if (error) return <Error />;
return (
<>
<h2 data-testid="AuthState">Authenticating...</h2>
<div className="spinner-border text-info" role="status">
<span className="sr-only">Loading...</span>
</div>
</>
);
}}
</Query>
</div>
);
}
EDIT 1:
Added relevant code in the problem. I checked the component and no one of this make a recursive call, and no make a infinite setState call. The setState problem is the consecuence to another problem.
<AnimatedSwitch
atEnter={bounceTransition.atEnter}
atLeave={bounceTransition.atLeave}
atActive={bounceTransition.atActive}
mapStyles={mapStyles}
className="route-wrapper"
>
<Route exact path="/" component={NoCredentials} />
<Route exact path="/token/:token" component={Auth} />
<Route path="/list-session" component={Home} />
</AnimatedSwitch>
and this is the code in Home component:
const Home = () => {
const [activeTab, setActiveTab] = useState("activeSessions");
return (
<ValidateToken>
<Container className="bg-light">
<div className="Home">
<CreateSessionComponent />
<Nav tabs>
<NavItem>
<NavLink
className={classnames({
active: activeTab === "activeSessions"
})}
onClick={() => setActiveTab("activeSessions")}
>
Active Sessions
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={activeTab}>
<TabPane tabId="activeSessions">
{activeTab === "activeSessions" && (
<div>
<SessionsListComponent status="ACTIVE" />
</div>
)}
</TabPane>
</TabPane>
</TabContent>
</div>
</Container>
</ValidateToken>
);
};
export default Home;

The "maximum update depth exceeded" error occurs when you are trying to update a variable multiple times until the stack size exceeds. This usually happens when u are trying to set a state inside render() or calling a method inside render which in turn sets the state. So check if there is a setState() call inside render method of your code.
Also this might happen because of setting the localStorage variable inside render. Try to move it out of render. But if you anyways need it you can use the componentDidMount() hook. That way all your variables will be available to you when your component is rendered.

try this it may work i think.
<Query
query={VALIDATE_USER}
variables={{ userId: token }}
onCompleted={(e)=>onCompleted(e)} //onCompleted throws an event
>

Related

React object undefined on render

I have an API that responds with the following object:-
[
{
"A":4,
"B":3,
"C":2,
"D":1,
}
]
I want to display the object data in the following component:-
export default function name() {
const dispatch = useDispatch();
const { postsInfo } = useSelector((state) => state.posts);
useEffect(() => {
dispatch(getPostsInfo());
}, [dispatch]);
console.log(postsInfo[0]);
return (
<>
<div className="db-wrapper">
<div className="cards-container">
<Card title={"A"} value={postsInfo[0].A} />
<Card title={"B"} value={postsInfo[0].B} />
<Card title={"C"} value={postsInfo[0].C} />
<Card title={"D"} value={postsInfo[0].D} />
</div>
</div>
</>
);
}
but whenever the page is rendered it throws an error in the console log and nothing is shown on the page.
Uncaught TypeError: Cannot read properties of undefined (reading '0')
This is happening because it's an asynchronous process and is being mounted before useEffect ends fetching the data and populates its state.
To deal with it you can use Optional Chaining:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
Array item access with optional chaining:
let arrayItem = arr?.[42];
Your component return will be like this:
return (
<>
<div className="db-wrapper">
<div className="cards-container">
<Card title={"A"} value={postsInfo?.[0].A} />
<Card title={"B"} value={postsInfo?.[0].B} />
<Card title={"C"} value={postsInfo?.[0].C} />
<Card title={"D"} value={postsInfo?.[0].D} />
</div>
</div>
</>
);
Welcome to asynchronous programming!
Dispatch and useEffect are asynchronous. This means that the render function continues to execute without waiting for the effect callback to complete and the dispatch to populate your redux state. As a result postsInfo is undefined on the initial render, and is only later populated on subsequent renders when the redux state changes.
Whenever you are initializing state in a react component via an asynchronous operation, your render function needs to correctly handle the state being uninitialized. The 'standard' way to do this is to return null if the state is uninitialized, like so:
if (!postsInfo) return null;

React Route protection with HOC PrivateRoute component with asynchronous jwt token validation

I am not very experienced with React yet and have been trying to approach the problem of securing specific routes from authentication.
For this purpose, I need to implement a HOC component, specifically a "PrivateRoute" component.
Some main generalities:
I'm developing with React 17.0.2 and then react-router-dom 6.2.1
I need to verify the presence of the token on a Redis server (in the cloud) and validate it.
The project is divided into two "modules," one in React for the frontend and one in node js/express, which exposes the server-side logic.
In the frontend module, the routes are implemented as follows:
return (<Router>
<div className="App">
<nav className="navbar navbar-expand-lg navbar-light fixed-top">
<div className="container">
<Link className="navbar-brand" to={"/"}>My Site</Link>
<div className="collapse navbar-collapse" id="navbarTogglerDemo02">
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="nav-link" to={"/sign-in"}>Login</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to={"/sign-up"}>Sign up</Link>
</li>
</ul>
</div>
</div>
</nav>
<Routes>
<Route exact path='/' element={<PrivateRoute><Home /></PrivateRoute>} />
<Route path="/sign-in" element={<Login />} />
<Route path="/sign-up" element={<SignUp />} />
<Route path="*" Component={<Login/>} status={403}/>
</Routes>
</div></Router>
As you can see, for example, I want the Home component to be protected, and therefore I implement the PrivateRoute component with which I wrap the Home component.
The implementation of the PrivateRoute component must validate any jwt token present in localStorage, passing it to an HTTP validation service exposed by the node/express module. The token is sent to the http service by passing the Authorization Bearer header.
The logic of retrieving from the localStorage and sending to the validation service is implemented with a useEffect (() => {}, []). Inside it is defined async function, so you can make the axios call with await. The function is always called from the body of useEffect. However, I cannot understand why updating the state variable created with useState seems not to update correctly (synchronization problem?). This does not seem to trigger the rendering update if the Home component is validated correctly (in the ternary operator, home is represented by props.children), rather than redirecting to the login page.
Below I report the implementation of the PrivateRoute:
export const PrivateRoute = (props) => {
console.log('props',props);
const [tokenValid, setTokenValid] = useState('');
useEffect(() => {
const checkRedisToken = async () => {
console.log('Access localStorage');
const ni_token = localStorage.getItem('ni_token');
console.log('Returned localStorage');
const config = { headers: { 'Authorization': `Bearer ${ni_token}` } };
console.log('Call axios');
const response = await axios.get('http://localhost:5000/identity/verifytoken', config);
const data = response.data;
console.log('After axios ', data);
setTokenValid(response.data);
}
checkRedisToken();
console.log('tokenValid', tokenValid);
}, []);
return (
tokenValid !== '' ?
props.children :
<Navigate to="/sign-in" />
)
}
Thank you in advance for your help in understanding my mistakes.
Thank you so much
I try to describe the solution I have adopted. The solution is the result of the reading of similar posts previously published and of readings scattered on the web.
As noted by #tromgy the problem of my first implementation is caused by the asynchronous call to the endpoint / identity / verifytoken.
The flow of execution, after the call, continues (waiting for a result) and the tokenValid state variable, even if set, seems not to be immediately detected by React and a new rendering is not invoked.
The correct solution seems to be to add an additional "loadingComplete" state.
The "loadingComplete" state seems to handle the stages where the component is not or is mounted waiting for the authenticated state to be ready and set as well.
The declaration of the routes has remained unchanged and for the sake of simplicity I show the code fragment below:
function App() {
return (<Router>
<div className="App">
<nav className="navbar navbar-expand-lg navbar-light fixed-top">
<div className="container">
<Link className="navbar-brand" to={"/"}>My Site</Link>
<div className="collapse navbar-collapse" id="navbarTogglerDemo02">
<ul className="navbar-nav ml-auto">
<li className="nav-item">
<Link className="nav-link" to={"/sign-in"}>Login</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to={"/sign-up"}>Sign up</Link>
</li>
</ul>
</div>
</div>
</nav>
<Routes>
<Route exact path='/' element={<PrivateRoute><Home /></PrivateRoute>} />
<Route path="/sign-in" element={<Login />} />
<Route path="/sign-up" element={<SignUp />} />
<Route path="*" Component={<Login/>} status={403}/>
</Routes>
</div></Router>
);
}
export default App;
As mentioned, the changes made to the PrivateRoute component are quite substantial. Before giving a brief description below, I report the new implementation below:
export const PrivateRoute = (props) => {
console.log('props', props);
const [authenticated, setAuthenticated] = useState(null);
const [loadingComplete, setLoadingComplete] = useState(false);
useEffect(() => {
const ni_token = localStorage.getItem('ni_token');
const isLogin = async (ni_token) => {
console.log('isLogin');
try {
const config = { headers: { 'Authorization': `Bearer ${ni_token}` } };
const result = await axios.get('http://localhost:5000/identity/verifytoken', config);
setAuthenticated(result.data);
} catch (e) {
console.log(e);
}
setLoadingComplete(true);
}
isLogin(ni_token);
}, []);
if(loadingComplete){
console.log('Completed', authenticated);
return (authenticated === 'Verify Token Respone' ?
props.children :
<Navigate to="/sign-in" />)
}else {
console.log('Loading...');
return (<div> Loading...</div>)
}
}
As you can see the useEffect with empty dependency array, so that the passed function is only executed once.
On the first call to the web application root we observe the execution of console.log ('props', props).
Immediately thereafter, the console.log trace shows ('Loading ...').
Immediately after, the "isLogin" log appears, in fact useEffect calls isLogin (ni_token). At this moment the asynchronous http call is made to the endpoint "/ identity / verifytoken" in charge of validating the token. At the first access to the frontend (http: // localhost: 3000) there is no token and the endpoint returns an http 403 (Forbidden) status. The authenticated state variable is not changed and its value remains "null", but lodingComplete is set to "true" causing <Navigate to = "sign-in /> to be returned, ie the redirect to the login page.
As you can see, the change in state determines the rendering of the component, the "props" log and the "Completed null" log reappear.
Below you can find what is logged at first access:
props Object
Loading ...
Failed to load resource: the server responded with a status of 403 (Forbidden)
props Object
Completed null
Some thougths for further deepening about not so clear aspects: if I do not misinterpret, at the new rendering the stream passes again through console.log ('props', props) and obviously react does not re-execute the useStates? Otherwise loadingComplete would be false again and would render Loading while as expected it goes through redirection to the login component. Is my interpretation correct?
If I finally log in and a positive authentication happens, the flow highlighted with the logs executed with console.log is the following:
props {children: {…}}
Loading ...
isLogin
props {children: {…}}
Loading ...
props {children: {…}}
Completed Verify Token Respone
If I'm not wrong, the re-rendering is done twice in addition to the first one. But there is something I can't quite understand. The useEffect is performed (log isLogin) the asynchronous http call is performed and setAuthenticated will be performed later the flow of execution certainly goes through setLoadingComplete (true) at this point I expect a re-rendering but contrary to what I expect I still see the "Loading" log ... But on the other hand if this is not the case it should still redirect to the Login component. Eventually, the "Completed Verify Token Respone" log appears at the setAuthenticated setting.
I'm still a bit puzzled who helps me understand what I see and what is happening? I hope this post can be useful to learn more about the mechanisms and functioning of React and of this specific need.

Calling props.myFunction() inside a function in react functional component

I am passing a function to a child component in React and it works perfectly when I call it directly in onClick of a button. But then when I move it into a separate function and call the function from onClick, it no longer works. I can verfity the function is being called with a log statement, but props.myFunction() never gets called. I've encountered this a few times now in React and it always confuses me.
I've looked at some other questions like this one, but its still not helping.
React: Can't call prop function when it is inside of another function?
This code works - it sets loggedIn to true in the parent when the button is clicked
export default function SignupModal(props) {
return (
<div class="main-block">
<button
className="create-account-button"
href="/"
onClick={props.setIsLoggedIn(true)}
>
Create Account
</button>
</div>
);
}
this code doesn't set loggedIn to true - but the function still gets called
export default function SignupModal(props) {
const createAccount = () => {
console.log("this gets logged");
//but this doesn't get called
props.setIsLoggedIn(true);
};
return (
<div class="main-block">
<button
className="create-account-button"
href="/"
onClick={createAccount}
>
Create Account
</button>
</div>
);
}
can anyone tell me why?
here is what I'm trying to do in the parent, maybe a little unorthodox to render routs like this but it's for a splash page - also as mentioned it works perfectly in onClick()
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<>
{isLoggedIn ? (
<>
<SearchBar onLocationChange={onLocationChange}></SearchBar>
<NavBar></NavBar>
<Switch>
<Route exact path="/quik">
<Pins
mapRef={mapRef}
pinnedLocationIds={pinnedLocationIds}
pinsRemaining={pinsRemaining}
pushPinnedLocation={pushPinnedLocation}
usePin={usePin}
mapCenter={mapCenter}
setMapCenter={setMapCenter}
matches={matches}
setMapZoom={setMapZoom}
mapZoom={mapZoom}
changeStatus={changeStatus}
/>
</Route>
<Route path="/potentials">
{/* dont forget,
the props for 'Potentials' must also pass
through 'potentials in the 'Pins' component! */}
<Potentials
pinsRemaining={pinsRemaining}
matches={matches}
changeStatus={changeStatus}
/>
</Route>
<Route path="/connects">
<Connects matches={matches} recentMatchId={recentMatchId} />
</Route>
<Route path="/profile" component={Profile} />
</Switch>
</>
) : (
<Route exact path="/quik">
<Landing setIsLoggedIn={() => setIsLoggedIn}></Landing>
</Route>
)}
</>
);
Just to demonstrate the difference between your two cases, take this example. When I call a function immediately in the returned JSX code, it fires as soon as the element mounts. However, in the second button, I have to click it before the logging will happen.
function App() {
return (
<div>
<button onClick={console.log("foo")}>Immediate log</button>
<button onClick={() => console.log("bar")}>Must click to log</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="main"></div>
Edit: In the case of your code, you're actually setting setIsLoggedIn to a function that returns a function when you pass it to your Landing component:
<Landing setIsLoggedIn={() => setIsLoggedIn} ></Landing>
What you want to do instead is this:
<Landing setIsLoggedIn={setIsLoggedIn} ></Landing>
Now setIsLoggedIn is just a function and your second example will work (your first example will fire immediately and not work how you intend).

useState hook being called inside map function causing infinite loop

I'm currently building a dynamic form engine and I want to display results from the redux store when the Answer Summary component is rendered. The way I figured would be best to do this would be to having a 'complete' status and set it to true once the answerSummary component is loaded, but doing this within the map function does not work and throws the infinite loop react error.
Code is here:
function App() {
let [complete, setComplete] = useState(false);
return (
<div>
<h1>Form App Prototype</h1>
<Router>
<Switch>
{Object.values(Database.steps).map(step => {
step.name === 'answerSummary' ? setComplete(true) : setComplete(false);
return (
<Route exact path={`/${step.name}`} render={() =>
<Step step={step} />
}
/>
)
})}
</Switch>
</Router>
<br></br>
<div style={{display: complete? 'block' : 'none'}}><StoreVisual/></div>
</div>
);
}
export default App;
EDIT: I know you aren't able to setState inside the render - I've written it this way as a way to try and convey what I want to be able to do
My understanding of your problem is that you are trying to display results after the answer summary component is mounted.
You can achieve this by using the useEffect hook which runs when the component mounts. https://reactjs.org/docs/hooks-effect.html
If you only want to render <StoreVisual/> when they are on the last step it might be easier to set up a state hook for the index of the step people are on.
const [index, setIndex] = useState(0);
Every time someone progresses a step increment this value.
You would have to pass setIndex, or whatever you call your setter, into the Step component to do this.
Then you can render <StoreVisual/> with a conditional, like so...
<div>
<h1>Claimer Form App Prototype</h1>
<Router>
<Switch>
{Object.values(Database.steps).map(step =>
<Route exact
path={`/${step.name}`}
render={() => <Step step={step} /> }/> )}
</Switch>
</Router>
<br></br>
{Database.steps[index] === 'answerSummary' && <StoreVisual/>}
</div>
This approach also affords you a simple way to let people start in the middle of the form. Say you want to let people save half-finished forms in the future, you just change/update the default value of index hook.
Instead of running that code inline in your return, build the array in your function logic:
function App() {
let [complete, setComplete] = useState(false);
// build an array of Route components before rendering
// you should also add a unique key prop to each Route element
const routes = Object.values(Database.steps).map(step => {
step.name === 'answerSummary' ? setComplete(true) : setComplete(false);
return <Route exact path={`/${step.name}`} render={() => <Step step={step} />} />
})
return (
<div>
<h1>Claimer Form App Prototype</h1>
<Router>
<Switch>
// render the array
{[routes]}
</Switch>
</Router>
<br></br>
<div style={{display: complete? 'block' : 'none'}}><StoreVisual/></div>
</div>
);
}
export default App;
I don't think you need to call setComplete(false) based on this logic, so you should probably replace your ternary with an if statement:
if (step.name === 'answerSummary') {
setComplete(true)
}
and avoid making unnecessary function calls
You cannot set state inside render which cause infinite loop.[Whenever state is changed, component will re-render(calls render function)]
render()=>setState()=>render()=>setState().......infinite
WorkAround:
<div style={{display: this.props.location.pathname=='answerSummary'? 'block' : 'none'}}><StoreVisual/></div>

Can't redirect to search result page

I'm not good at English, so it might be hard to explain my intention.
I'm using React, React router, Apollo client,
In production build, When I click the search button, I can't redirect to render a result component because of error with error message
error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
First, I tried without useEffect. It occur error as described above.
Second, I tried with useEffect hook, that change redirect state to false. and it changes url. but doesn't render result component.
useEffect(() => {setRedirect(false)}, [redirect])
Finally I tried to make another react app to test this situation without apollo, and some custom components that are for clean code in the production build. I tried in development build. And it perfectly works without error
// search-bar.js
function SearchBar(props) {
// keywords for searching books
const [keywords, setKeywords] = useState('');
// search option
const [option, setOption] = useState('All');
// determine to redirect or not
const [redirect, setRedirect] = useState(false);
return (
<>
<div className="nav-search">
<form
role="search"
onSubmit={ e => {
e.preventDefault();
setRedirect(true);
}}
className="form-search"
>
<NotRequiredInput
type="search"
label="Search keywords: "
id="keywords"
value={keywords}
onChange={e => {
setKeywords(e.target.value);
}}
/>
// it map the list's items to option
<SelectBoxWithoutNone
label="Search option: "
id="search-option"
value={option}
onChange={e => {
setOption(e.target.value);
}}
list={['All', 'Title', 'Author']}
/>
<SubmitButton label="Search" />
</form>
</div>
{ redirect && <Redirect to={`/search?o=${option}&k=${keywords}`} /> }
</>
);
}
// app.js
<Query>
{({loading, data}) => {
if (loading)
return (
<Header>
<NavBar>
<main>
<Suspense>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/search" render={props => <SearchResult {...props}/>}
)
}}
// app.js looks like this with many route component.
// also using React.lazy for code splitting
// search-result.js
function SearchResult (props) {
const parsed = queryString.parse(props.location.search);
const option = parsed.o;
const keywords = parsed.k;
return (
<div className="div-search-result">
<p>{option}</p>
<p>{keywords}</p>
</div>
);
}
I expected that it renders result component (with or without hooks)
but as I described above, It occurred error
Update: When I tried to type some query parameter on url path directly, it works.

Resources