useEffect local state behavior after it get updated - reactjs

In this reactjs doc example about custom hooks
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
we suppose tnhat is called by this Foo component
function Foo(props) {
const [state, setState] = useState({...});
...
useFriendStatus(friendID)
...
}
When handleStatusChange is invoked and the useEffect local state isOnline change, what will happend ?
will the Foo component get rendered more or less immediately (i know setState() is asynch) or it will wait until its own state or props gets updated ?
in my knowledge a custom hook will be invoked only when the component which call it is rendered or am i wrong ?

When handleStatusChange is invoked and the useEffect local state isOnline change, what will happend ?
will the Foo component get rendered more or less immediately (i know setState() is asynch) or it will wait until its own state or props gets updated ?
Setting state is what causes react components to rerender. As soon as you call setIsOnline, the component's state has been updated, and it rerenders more or less immediately.
in my knowledge a custom hook will be invoked only when the component which call it is rendered or am i wrong ?
Custom hooks are a convenience: they let you reuse code, or simply make your code more readable. But they don't change what react knows. React just knows that you called useState, useEffect, and later setIsOnline. It doesn't know whether that code is written inline or extracted to a helper method.
If React sees a call to useState while rendering, it will set up a state variable and a setter function. If you call that setter function, it will rerender the component.

Related

Why is the updated state variable not shown in the browser console?

I understand that the method returned by useState is asynchronous, however, when I run the this code, I am delaying the console.log by upto 5 seconds, but it still logs the previous value and not the updated value of the state variable. The updated value would be 2, but it still logs 1. In the react developer tools however, I can see the state changing as I press the button, though I am curious to know why after even such a delay the console prints an obsolete value? This is not the case with class components and setState but with function components and useState.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
setTimeout(() => {
console.log(variable);
}, 2000);
};
return <button onClick={handleClick}>Button</button>;
}
export default App;
In your code your setTimeout is getting the variable value from the closure at the time it was invoked and the callback function to the setTimeout was created. Check this GitHub issue for the detailed explanation.
In the same issue, they talk about utilizing useRef to do what you are attempting. This article by Dan Abramov packages this into a convenient useInterval hook.
State updates are asynchronous. That means, that in order to view the new value, you need to log It on the next render using useEffect and adding it to the dependencies array:
In this example, give a look at the order the logs appear:
First, you will have the current one, and once triggered, you will have the new value, and then it will become the 'old value' until triggered again.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => { console.log(`new state rolled: ${counter}`);
}, [counter]);
console.log(`Before rolling new State value: ${counter}`);
const handleClick = () => setCounter(counter++)
return <button onClick={handleClick}>Button</button>;
}
export default App;
Another technic to console.log a value afterward a state change is to attach a callback to the setState:
setCounter(counter++, ()=> console.log(counter));
I hope it helps.
A state take some time to update. The proper way to log state when it updates, is to use the useEffect hook.
setTimeout attaches the timer and wait for that time, but it will keep the value of variable from the beginning of the timer, witch is 1
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
};
useEffect(() => {
console.log(variable);
}, [variable]);
return <button onClick={handleClick}>Button</button>;
}
export default App;
This is not the case with class components and setState but with
function components and useState
In class components, React keep the state in this.state & then call the Component.render() method whenever its need to update due to a setState or prop change.
Its something like this,
// pseudocode ( Somewhere in React code )
const app = MyClassComponent();
app.render();
// user invoke a callback which trigger a setState,
app.setState(10);
// Then React will replace & call render(),
this.state = 10;
app.render();
Even though you cannot do this.state = 'whatever new value', React does that internally with class components to save the latest state value. Then react can call the render() method and render method will receive the latest state value from this.state
So, if you use a setTimeout in a class component,
setTimeout(() => {
console.log(this.state) // this render the latest value because React replace the value of `this.state` with latest one
}, 2000)
However in functional component, the behaviour is little bit different, Every time when component need to re render, React will call the component again, And you can think the functional components are like the render() method of class components.
// pseudocode ( Somewhere in React code )
// initial render
const app = MyFuctionalComponent();
// state update trigger and need to update. React will call your component again to build the new element tree.
const app2 = MyFunctionalComponent();
The variable value in app is 1 & variable value in app2 is 2.
Note: variable is just a classic variable which returned by a function that hooked to the component ( The value save to the variable is the value return by the hook when the component was rendering so it is not like this.state i.e its hold the value which was there when the component is rendering but not the latest value )
Therefore according to the Clouser, at the time your setTimeout callback invoke ( Which was called from app ) it should log 1.
How you can log the latest value ?
you can use useEffect which getting invoke once a render phase of a component is finished. Since the render phase is completed ( that mean the local state variables holds the new state values ) & variable changed your console log will log the current value.
useEffect(() => {
console.log(variable);
}, [variable])
If you need the behaviour you have in class components, you can try useRef hook. useRef is an object which holds the latest value just like this.state but notice that updating the value of useRef doesn't trigger a state update.
const ref = useRef(0);
const handleClick = () => {
setVariable(2); // still need to setVariable to trigger state update
ref.current = 2 // track the latest state value in ref as well.
setTimeout(() => {
console.log(ref.current); // you will log the latest value
}, 2000);
};

How do I call useEffect when the trigger is in another component?

I am trying to fetch data in a functional React component using useEffect (since the function is asynchronous) and I compute and return the data in h2 tags that was fetched in this component. However, I want to fetch the data when in another component (App.js) I hit the enter key. I have an enter key handler which calls a useState which I believe should re-render the component but since the useEffect is in another component, I am not getting the calling of the useEffect as I intend. What is the best method to press the Enter key and have useEffect in another component run?
function handleKeyDown(event){
if(event.key === 'Enter'){
//this setHeadingText is a useState variable which should re-render in App.js
setHeadingText(name);
}
It sounds like your useEffect is specifying an empty dependency array (instructing to run only once):
useEffect(() => {
//fetch some data
}, []); // This empty array ensures it only runs 1 time
Have a look at this section in the docs: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
If your second component has access to your headingText state, try specifying your headingText as a dependency to the useEffect, and any changes to it should trigger your useEffect
useEffect(() => {
//fetch some data
}, [headingText]); // Only re-run the effect if headingText changes
Alternatively, remove the dependency array from your useEffect.
Note: this will cause the useEffect to run on every re-render
useEffect(() => {
//fetch some data
}); // run every time the component re-renders
Now that I have understood properly, I think this is the solution you wished for:
The headingText value that is being updated in your App.jsx component should be passed down to your Child component with the help of props. So use <Child headingText={headingText} /> while loading the Child component in your App.jsx.
Inside your Child component, receive the value like this function Child(props) or function Child({ headingText }) if you have more values to pass, prefer props.
Now you can easily access the changes in value made in your App component inside your Child component with the help of props.headingText or headingText respective to the way you defined your Child in point 2.
To re-render your Child component, you will now use the useEffect hook with its dependency set to headingText, like:
React.useEffect(() =>
{
// Code for refetching
}
, [headingText]); // or props.headingText if your Child component uses (props)
For example: CodeSandbox
Hope that helps you!
you can make a conditional rendering with the other component, so it get rendered only if you press Enter which would invoke an event:
//App.js
import {AnotherComponent} from './anothercomponent.js' //assuming the file is in src folder
function RenderOnEnter({pressed}){
if(pressed){
return (
<AnotherComponent/>
)
}
return null
}
function App(){
const [pressed,setPressed] = useState(false)
function handlePressed(e){
if(e.target.key === 'Enter'){
setPressed(True)
}
else{
setPressed(False)
}
}
return(
<div>
<button onClick={(e)=>handlePressed(e)}>Click me to obtain data!</button>
<RenderOnPress pressed={pressed}/>
</div>
)
}

React with TypeScript - React has detected a change in the order of Hooks called by ComponentName

I am working with a project with users. Right now I am working with the UserProfile
This is the error I am recieving.
React has detected a change in the order of Hooks called by UserProfile
Previous render Next render
------------------------------------------------------
1. useState useState
2. useContext useContext
3. useEffect useEffect
4. undefined useContext
Let me show some code of the UserProfile component.
export const UserProfile = () => {
document.title = `${title} - My Profile`;
const [profile, setProfile] = useState<UserDetails>();
const {claims} = useContext(AuthContext);
const getUserEmail = (): string => {
return claims.filter(x => x.name === "email")[0]?.value.toString();
}
useEffect(() => {
axios.get(`${urlAuth}?userName=${getUserEmail()}`)
.then((response: AxiosResponse<UserDetails>) => {
setProfile(response.data);
})
}, [getUserEmail]);
return (
profile ?
<article>
<h1>This profile belongs to {UserName()}</h1>
<h2>{profile.name}</h2>
</article>
: <div>Loading...</div>
)
}
I get a warning at the getUserEmail function,
It says
The 'getUserEmail' function makes the dependencies of useEffect Hook (at line 26) change on every render.
Move it inside the useEffect callback.
Alternatively, wrap the definition of 'getUserEmail' in its own useCallback() Hook.
I am not sure on how this should be done.
Any ideas on what I could do?
Thanks
Wrap getUserEmail's value in a useCallback.
On every render, getUserEmail essentially becomes a 'new' function.
When there's a function in the deps array of a useEffect or other such hooks, React checks it by reference. Since each component function execution/rerender leads to the creation of a new function, your useEffect hook will actually run every single time, sending you into a re-render loop (because it'll run the useEffect, update the state with setProfile, which in turn will trigger another execution, where getUserEmail is different again, leading to the useEffect to run again and so on).
const getUserEmail = useCallback((): string => {
return claims.filter(x => x.name === "email")[0]?.value.toString();
}, [claims]);
This should give you a memoized callback that will only be recreated if claims changes. Since claims comes from your context, this should be safe as a dependency.
The reason why you are getting error about order of hooks is I think because of this:
profile ?
<article>
<h1>This profile belongs to {UserName()}</h1>
<h2>{profile.name}</h2>
</article>
: <div>Loading...</div>
If UserName is a component you should not call it as function, rather as element <UserName/>. When you call it as function react thinks some of the hooks which you call inside it belong to the parent component - this combined with condition profile ? could give you the error.

How to avoid the memory leak warning in my react application, even though i am cleaning up my useEffect?

Warning: Can't perform React state update on the unmounted component
App.js
App.js
App.js
After using the useIsMount approach, i am unable to pass the state value as props
This happens when you try to set state on a component that has already unmounted. This is commonly seen in async operations where you make an async request, and then you try to set state once it resolves.
The process goes like this:
Component mounts
useEffect runs and fires the async request
The async function creates a closure around the set state function
The component unmounts (due to a change in UI for whatever reason)
The request resolves
The closured set state function is called - but the component that contains the state has already unmounted! Error happens here.
To fix this, you need to check to see if the component is still mounted before you call the state update function.
You can check to see if a component is mounted by using something like a useIsMounted hook. There are many examples of this hook online.
HMR commented with a link to a good useIsMounted hook, which I'l post here:
import { useRef, useEffect } from 'react';
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
};
export default useIsMounted;
To use the hook, you call it in your component:
const isMounted = useIsMounted()
And inside your async function, check to see if the component is mounted before setting state:
if(isMounted.current) {
// it's okay to set state here
}
Make sure you add the isMounted variable as a dependency of your useEffect so you have the most up to date value!

What does it mean to 'move this variable directly inside useEffect' in this error message?

I am attempting to pass an object through props to a child component. The value is set in the useEffect hook and lost when passed to my child component.
I have tried setting the value of the object outside the useEffect hook in a separate function but the value is still lost.
import React, { useState, useEffect } from 'react';
function SetValue(props){
let users = {};
useEffect(() => {
users = { user: 'bob' };
})
return <NewComponent users={users} />
}
export default SetValue;
I expected props.users to be { user: 'bob' } and not an empty object {}.
The error message is:
"Assignments to the 'users' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect react-hooks/exhaustive-deps"
About useEffect hook:
By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. more
This means that function inside useEffect will be called after rendering of the component. That's why you have an empty object.
About the error. You have it because React doesn't remember your users variable - it will be recreated on each render of SetValue component. It will be better to use useState hook to set value in useEffect and remember it on the next render.
And one more note. Don't forget about passing the second argument in useEffect with an array of dependencies. Now your hook will be called after each render of SetValue component.
Here's how to use useState hook:
import React, { useState, useEffect } from 'react';
function SetValue(props){
const [users, setUsers] = useState({});
useEffect(() => {
setUsers({ user: 'bob' });
}, [
//here you could pass dependencies, or leave it empty to call this effect only on first render
]);
return <NewComponent users={users} />
}
export default SetValue;
This warning --"Assignments to the 'users' variable from inside React Hook useEffect will be lost after each render-- is the reason why state concept exists in React. React component doesn't preserve the value of normal javascript variables throughout successive re-renders(component lifecycle).Thats where react state helps so that changes to the state can be reflected in DOM if component re-renders.

Resources