React Hook SetState Causing Infinite Loop - reactjs

Below is my code, I am attempting to call setState inside of my logout button as this button only renders when Auth0 isLoggedIn is true.
This seems to be causing an infinite loop, my guess is because the Auth0 API is being called
every time. I am following this tutorial: https://www.youtube.com/watch?v=35lXWvCuM8o
The only difference is I am attempting to call setState without an onClick function,
can anyone please explain to me the issue and a fix, thank you in advance!
I understand I can use localStorage to maintain a users Logged in Session, but I am doing this purely to learn React Hooks,
import React, { useState, useContext, useCallback } from "react";
import { useAuth0 } from "#auth0/auth0-react";
import { ValueContext } from "./ValueContext";
const LogoutButton = () => {
const [login, setLogin] = useContext(ValueContext);
const { logout, isAuthenticated } = useAuth0();
const updateLogin = () => {
setLogin({ loggedIn: "false" });
};
return (
isAuthenticated && (
<>
<button onClick={() => logout()}>Log Out</button>
<h1>{login.loggedIn}</h1>
{updateLogin()}
</>
)
);
};

You're calling updateLogin() inside of your render function, so you're setting loggedIn: true every render. Don't put functions or console logs directly inside of the return function, put them inside of the main LogoutButton function body if you must. But updateLogin() should only be called inside of some onPress.

You call updateLogin() every time the component renders. This sets the state variabel loggedIn. This will cause the component to render again and the process keeps looping.

The only difference is I am attempting to call setState without an onClick function
This is a big difference! setState should not be called in render function's main body, or at least, call it conditionaly. Otherwise you will get setState -> render -> setState -> render ->... it's endless.
You can try this:
let alreadySet = false;
const LogoutButton = () => {
const [login, setLogin] = useContext(ValueContext);
const { logout, isAuthenticated } = useAuth0();
const updateLogin = () => {
setLogin({loggedIn:'false'});
};
if(!alreadySet){
alreadySet = true;
updateLogin()
}
return (
isAuthenticated && (
<>
<button onClick={() => logout()}>
Log Out
</button>
<h1>{login.loggedIn}</h1>
</>
)
Notice this
let alreadySet = false;
....
{
if(!alreadySet){
alreadySet = true;
updateLogin()
}
}
It means loggedIn will only be updated once. Is it what you want? If it's not. You can updated your question with more details :)

If you need to use updateLogin when isAuthenticated is true, you can add useEffect hook.
like this
useEffect(()=>{ if (isAuthenticated){
setLogin({loggedIn:'false'});
}},[isAuthenticated, setLogin] );

Related

Unexpected behaviour of setInterval function (interval keeps on decreasing) [duplicate]

Are there ways to simulate componentDidMount in React functional components via hooks?
For the stable version of hooks (React Version 16.8.0+)
For componentDidMount
useEffect(() => {
// Your code here
}, []);
For componentDidUpdate
useEffect(() => {
// Your code here
}, [yourDependency]);
For componentWillUnmount
useEffect(() => {
// componentWillUnmount
return () => {
// Your code here
}
}, [yourDependency]);
So in this situation, you need to pass your dependency into this array. Let's assume you have a state like this
const [count, setCount] = useState(0);
And whenever count increases you want to re-render your function component. Then your useEffect should look like this
useEffect(() => {
// <div>{count}</div>
}, [count]);
This way whenever your count updates your component will re-render. Hopefully this will help a bit.
There is no exact equivalent for componentDidMount in react hooks.
In my experience, react hooks requires a different mindset when developing it and generally speaking you should not compare it to the class methods like componentDidMount.
With that said, there are ways in which you can use hooks to produce a similar effect to componentDidMount.
Solution 1:
useEffect(() => {
console.log("I have been mounted")
}, [])
Solution 2:
const num = 5
useEffect(() => {
console.log("I will only run if my deps change: ", num)
}, [num])
Solution 3 (With function):
useEffect(() => {
const someFunc = () => {
console.log("Function being run after/on mount")
}
someFunc()
}, [])
Solution 4 (useCallback):
const msg = "some message"
const myFunc = useCallback(() => {
console.log(msg)
}, [msg])
useEffect(() => {
myFunc()
}, [myFunc])
Solution 5 (Getting creative):
export default function useDidMountHook(callback) {
const didMount = useRef(null)
useEffect(() => {
if (callback && !didMount.current) {
didMount.current = true
callback()
}
})
}
It is worth noting that solution 5 should only really be used if none of the other solutions work for your use case. If you do decide you need solution 5 then I recommend using this pre-made hook use-did-mount.
Source (With more detail): Using componentDidMount in react hooks
There's no componentDidMount on functional components, but React Hooks provide a way you can emulate the behavior by using the useEffect hook.
Pass an empty array as the second argument to useEffect() to run only the callback on mount only.
Please read the documentation on useEffect.
function ComponentDidMount() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log('componentDidMount');
}, []);
return (
<div>
<p>componentDidMount: {count} times</p>
<button
onClick={() => {
setCount(count + 1);
}}
>
Click Me
</button>
</div>
);
}
ReactDOM.render(
<div>
<ComponentDidMount />
</div>,
document.querySelector("#app")
);
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
useEffect() hook allows us to achieve the functionality of componentDidMount, componentDidUpdate componentWillUnMount functionalities.
Different syntaxes of useEffect() allows to achieve each of the above methods.
i) componentDidMount
useEffect(() => {
//code here
}, []);
ii) componentDidUpdate
useEffect(() => {
//code here
}, [x,y,z]);
//where x,y,z are state variables on whose update, this method should get triggered
iii) componentDidUnmount
useEffect(() => {
//code here
return function() {
//code to be run during unmount phase
}
}, []);
You can check the official react site for more info. Official React Page on Hooks
Although accepted answer works, it is not recommended. When you have more than one state and you use it with useEffect, it will give you warning about adding it to dependency array or not using it at all.
It sometimes causes the problem which might give you unpredictable output. So I suggest that you take a little effort to rewrite your function as class. There are very little changes, and you can have some components as class and some as function. You're not obligated to use only one convention.
Take this for example
function App() {
const [appointments, setAppointments] = useState([]);
const [aptId, setAptId] = useState(1);
useEffect(() => {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = aptId;
console.log(aptId);
setAptId(aptId + 1);
return item;
})
setAppointments(apts);
});
}, []);
return(...);
}
and
class App extends Component {
constructor() {
super();
this.state = {
appointments: [],
aptId: 1,
}
}
componentDidMount() {
fetch('./data.json')
.then(response => response.json())
.then(result => {
const apts = result.map(item => {
item.aptId = this.state.aptId;
this.setState({aptId: this.state.aptId + 1});
console.log(this.state.aptId);
return item;
});
this.setState({appointments: apts});
});
}
render(...);
}
This is only for example. so lets not talk about best practices or potential issues with the code. Both of this has same logic but the later only works as expected. You might get componentDidMount functionality with useEffect running for this time, but as your app grows, there are chances that you MAY face some issues. So, rather than rewriting at that phase, it's better to do this at early stage.
Besides, OOP is not that bad, if Procedure-Oriented Programming was enough, we would never have had Object-Oriented Programming. It's painful sometimes, but better (technically. personal issues aside).
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Please visit this official docs. Very easy to understand the latest way.
https://reactjs.org/docs/hooks-effect.html
Info about async functions inside the hook:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:
useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or state
useLayoutEffect hook is the best alternative to ComponentDidMount in React Hooks.
useLayoutEffect hook executes before Rendering UI and useEffect hook executes after rendering UI. Use it depend on your needs.
Sample Code:
import { useLayoutEffect, useEffect } from "react";
export default function App() {
useEffect(() => {
console.log("useEffect Statements");
}, []);
useLayoutEffect(() => {
console.log("useLayoutEffect Statements");
}, []);
return (
<div>
<h1>Hello Guys</h1>
</div>
);
}
Yes, there is a way to SIMULATE a componentDidMount in a React functional component
DISCLAIMER: The real problem here is that you need to change from "component life cycle mindset" to a "mindset of useEffect"
A React component is still a javascript function, so, if you want something to be executed BEFORE some other thing you must simply need to execute it first from top to bottom, if you think about it a function it's still a funtion like for example:
const myFunction = () => console.log('a')
const mySecondFunction = () => console.log('b)
mySecondFunction()
myFunction()
/* Result:
'b'
'a'
*/
That is really simple isn't it?
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be mapped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
And in this specific case it's true. But what happens if I do something like that:
const MyComponent = () => {
const someCleverFunction = () => {...}
someCleverFunction() /* there I can execute it BEFORE
the first render (componentWillMount)*/
useEffect(()=> {
someCleverFunction() /* there I can execute it AFTER the first render */
},[]) /*I lie to react saying "hey, there are not external data (dependencies) that needs to be maped here, trust me, I will leave this in blank.*/
return (
<div>
<h1>Hi!</h1>
</div>
)}
This "cleverFunction" we are defining it's not the same in every re-render of the component.
This lead to some nasty bugs and, in some cases to unnecessary re-renders of components or infinite re-render loops.
The real problem with that is that a React functional component is a function that "executes itself" several times depending on your state thanks to the useEffect hook (among others).
In short useEffect it's a hook designed specifically to synchronize your data with whatever you are seeing on the screen. If your data changes, your useEffect hook needs to be aware of that, always. That includes your methods, for that it's the array dependencies.
Leaving that undefined leaves you open to hard-to-find bugs.
Because of that it's important to know how this work, and what you can do to get what you want in the "react" way.
const initialState = {
count: 0,
step: 1,
done: false
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'doSomething') {
if(state.done === true) return state;
return { ...state, count: state.count + state.step, state.done:true };
} else if (action.type === 'step') {
return { ...state, step: action.step };
} else {
throw new Error();
}
}
const MyComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
dispatch({ type: 'doSomething' });
}, [dispatch]);
return (
<div>
<h1>Hi!</h1>
</div>
)}
useReducer's dispatch method it's static so it means it will be the same method no matter the amount of times your component is re-rendered. So if you want to execute something just once and you want it rigth after the component is mounted, you can do something like the above example. This is a declarative way of do it right.
Source: The Complete Guide to useEffect - By Dan Abramov
That being said if you like to experiment with things and want to know how to do it "the imperative wat" you can use a useRef() with a counter or a boolean to check if that ref stores a defined reference or not, this is an imperative approach and it's recommended to avoid it if you're not familiar with what happen with react behind curtains.
That is because useRef() is a hook that saves the argument passed to it regardless of the amount of renders (I am keeping it simple because it's not the focus of the problem here, you can read this amazing article about useRef ). So it's the best approach to known when the first render of the component happened.
I leave an example showing 3 different ways of synchronise an "outside" effect (like an external function) with the "inner" component state.
You can run this snippet right here to see the logs and understand when these 3 functions are executed.
const { useRef, useState, useEffect, useCallback } = React
// External functions outside react component (like a data fetch)
function renderOnce(count) {
console.log(`renderOnce: I executed ${count} times because my default state is: undefined by default!`);
}
function renderOnFirstReRender(count) {
console.log(`renderOnUpdate: I executed just ${count} times!`);
}
function renderOnEveryUpdate(count) {
console.log(`renderOnEveryUpdate: I executed ${count ? count + 1 : 1} times!`);
}
const MyComponent = () => {
const [count, setCount] = useState(undefined);
const mounted = useRef(0);
// useCallback is used just to avoid warnings in console.log
const renderOnEveryUpdateCallBack = useCallback(count => {
renderOnEveryUpdate(count);
}, []);
if (mounted.current === 0) {
renderOnce(count);
}
if (mounted.current === 1) renderOnFirstReRender(count);
useEffect(() => {
mounted.current = mounted.current + 1;
renderOnEveryUpdateCallBack(count);
}, [count, renderOnEveryUpdateCallBack]);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(prevState => (prevState ? prevState + 1 : 1))}>TouchMe</button>
</div>
);
};
class App extends React.Component {
render() {
return (
<div>
<h1>hI!</h1>
</div>
);
}
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<MyComponent/>
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you execute it you will see something like this:
You want to use useEffect(), which, depending on how you use the function, can act just like componentDidMount().
Eg. you could use a custom loaded state property which is initially set to false, and switch it to true on render, and only fire the effect when this value changes.
Documentation
the exact equivalent hook for componentDidMount() is
useEffect(()=>{},[]);
hope this helpful :)

React useState is initially unchanged

Take this piece of code:
import React from 'react';
import { useState, useEffect } from 'react'
export function App() {
let [isAdmin, setAdmin] = useState(false)
const checkIfAdmin = async() => {
setAdmin(true)
}
useEffect(() => {
checkIfAdmin()
}, []);
console.log(isAdmin)
return (
<div className='App'>
<h1>test</h1>
</div>
);
}
When console logging isAdmin, it comes out as false initially, but when checked again (such as in an onClick() event), it comes out as true. Why does it take 2 checks to finally output the desired result? How can I make it so that in checkIfAdmin the changes immediately take place, and isAdmin comes out as true on the first time?
Passing a function to setAdmin() to toggle in between states will immediately update the state variable.
const checkIfAdmin = () => {
setAdmin(isAdmin => !isAdmin)
}
You should be getting 2 console.logs, one that returns false and another that returns true. This is due to the way useEffect, and setState work.
When useEffect is used with an empty array as the second arg, it only runs once when the component mounts. And on the first mount of your component, isAdmin is initialized to false
useEffect(() => {
checkIfAdmin()
}, []);
Once setAdmin(true) executes, it updates the dom, causing the component to render for a second time, with the isAdmin value being true
You can visualize how the dom is being updated by adding another console.log() in your useEffect.
useEffect(() => {
console.log('component is mounting...')
checkIfAdmin();
}, []);

Avoid update on an unmounted component

I read a lot of similar questions but it seems my questions is a little different.
I am trying to login and I get the following error.
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
I am attaching the AuthContext.js that handles all the logic hoping you can explain me what is wrong and what knowledge it indicates I need to learn.
import React, { useContext, useState } from 'react'
import {postData} from '../adapters/authAdapter';
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({children}) {
const [currentUser,setCurrentUser] = useState(null);
async function login(email,password) {
const adaptedRes = await postData('log-in',{email,password});
if(adaptedRes.err) {
throw new Error(adaptedRes.message)
} else {
return setCurrentUser(adaptedRes.data);
}
}
const value = {
currentUser,
login
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
Thank you in advance
The error means that your setCurrentUser() function is being called, when AuthProvider is not currently mounted. That's why, use useRef() to check if your AuthProvider is mounted, and then set state as mentioned in the link:
export function AuthProvider({children}) {
const [currentUser,setCurrentUser] = useState(null);
const isMounted = useRef(false);
useEffect(() => {
// Becomes true on component mount
isMounted.current = true;
// becomes false on unmount
return () => {isMounted.current = false;}
}, [])
async function login(email,password) {
const adaptedRes = await postData('log-in',{email,password});
if(adaptedRes.err) {
throw new Error(adaptedRes.message)
} else {
if(!isMounted.current) {
// Check if component is mounted
console.log('component is not mounted');
return;
}
return setCurrentUser(adaptedRes.data);
}
}
const value = {
currentUser,
login
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
Update As Drew Pointed out:
Issue is as others say, the AuthProvider component is being unmounted before setCurrentUser is called. Instead of putting a band-aid fix in to keep the state update from occurring you should figure out why your auth provider isn't mounted. This is one of those provider components you typically want wrapping your entire app and mounted at all times. My guess is you've narrowed the scope of this auth provider too much to just around your auth component UI and then you navigate elsewhere and the provider is no longer available.
So, you should also check this, as if you are getting this error, then there is implementation problem somewhere.

Does a React Context update on history.push('/')

I'm using a React Context const Context = React.createContext() with a useEffect hook to set a variable in my outer-most component that wraps my entire app. On a child component within my app I am using history.push('/') to route back to the root. This does not appear to trigger an update on my Context variable. Is this expected? If so, is there a better way to route that would update my context variable?
I'm using react 16.14.0 & react-router-dom 5.2.0.
For example, in the code below. Shouldnt var increment on history.push('/')
Context.js
const Context= React.createContext();
const ContextProvider= (props) => {
const [var, setVar] = useState(null);
useEffect(() => {
setVar(var++)
}, []);
return (
<Context.Provider value={user}>{props.children}</Context.Provider>
);
};
ChildComponent.js
...
import { useHistory } from "react-router-dom";
...
const ChildComponent = () => {
const history = useHistory();
function doSomething(){
history.push('/')
}
}
return(
<Button onClick={() => doSomething()} />
)
This does not appear to trigger an update on my Context variable. Is this expected?
Changing the history does not cause context providers to rerender. You mention you have a useEffect, and in principle you could write some code in that useEffect which would listen to the history, and when it gets a change it sets state to cause a rerender. If you have code in there that you think is supposed to listen for history changes, feel free to share that and i'll comment on it.
However, instead of writing your own code to listen for changes, i'd recommend using the hooks provided by react-router. The useHistory and useLocation hooks will both listen for changes and rerender the component.
const Example = () => {
// When the location changes, Example will rerender
const location = useLocation();
const [someState, setSomeState] = useState('foo');
useEffect(() => {
if (/* check something you care about in the location */) {
setSomeState('bar');
}
}, [location]);
return (
<Context.Provider value={someState}>
{children}
</Context.Provider>
)
}

In React what's the best way to render a Component differently the first time it appears on the page?

I have some React Components that are going to be used a lot all over my App.
I'd like that only the first time they get printed in the DOM, they return as
<symbol id="SVGComponent"><SVGComponent></symbol><use href="#SVGComponent" />
while every other following printing should be only
<use href="#SVGComponent" /> since the symbol has already been declared in the page
const [firstTime, setFirstTime] = useState(true);
useEffect(() => { setFirstTime(false) }, []);
if (firstTime) {
return (<symbol id="Component"><Path /></symbol>);
}
else {
return(<use href="#Component"/>);
}
This is my code so far but the condition always return false.
How is that possibile in React with useEffect?
Thanks in advance for your help
It is not that your condition always returns false. The issue is that you change the state immediately after your first render(in the useEffect where you set the state variable value to false) which causes another immediate rerender so you skip the true condition of the variable(you technically don't skip it, you just render the true state and rerender with false straight away after).
If you don't change the state in the mounting useEffect you'll notice that the code works properly. In order to do this, though, you'll have to use useRef which basically enables you to create a variable that persists between rerenders but the mutation of its value doesn't cause a rerender(unlike state variables from useState).
I've added a button to simulate change of state or in other words - simulate a second render:
import React, { useState, useRef, useEffect } from 'react';
function App() {
const [buttonClicked, setButtonClicked] = useState(false);
const isFirstRender = useRef(true);
useEffect(() => {
isFirstRender.current = false;
}, []);
const renderRepresentation = () => {
if (isFirstRender.current) {
return (
<symbol id="Component">
<Path />
</symbol>
);
}
else {
return (
<use href="#Component" />
);
}
}
return (
<>
{renderRepresentation()}
<button onClick={() => setButtonClicked(true)}>SIMULATE STATE CHANGE</button>
</>
);
}
And here is a codesandbox demo which you can try.
The component itself won't be able to tell if others of itself exist. You will need to store this information at a higher level in the application, and then tell the component via props whether it is the first instance or not.
So I ended up replying to myself again for anyone coming. Fortunately what I want to achieve is possibile with just a few lines of code. Thanks this guy for the code: https://stackoverflow.com/a/57331421/3937587
let instancesCount = 0
function Component(props) {
const [firstRender, setFirstRender] = useState(false)
useEffect(() => {
setFirstRender(instancesCount === 0)
instancesCount++
return () => { instancesCount-- }
}, [])
if (firstRender)
//do something only the first time the component appears
}

Resources