I am new to React and I am a little confused about what gets updated every time the state or props change. For instance:
const Foo = props => {
const [someState, setSomeState] = useState(null)
let a
useEffect(() => {
a = 'Some fetched data'
}, [])
}
Now if the state (i.e. someState) or props get updated, does it run through the function again, making a undefined? Does only JSX elements that depend on the state/props and the hooks that use them get affected? What changes exactly?
To understand how the state affect your component, you could check this example on Stackblitz. It shows you how the local variable of your component act when a state changes. This behavior is exactly the same when the component receive a new prop. Here is the code just in case :
import React, { Component } from "react";
import { render } from "react-dom";
const App = () => {
const [state, setState] = React.useState('default');
// see how the displayed value in the viewchanges after the setTimeout
let a = Math.floor(Math.random() * 1000);
React.useEffect(() => {
a = "new a"; // Does nothing in the view
setTimeout(() => setState("hello state"), 2000);
}, []);
return (
<div>
{a} - {state}
</div>
);
};
render(<App />, document.getElementById("root"));
What you can see is :
a is initialized with a random value and it is displayed in the view
useEffect is triggered. a is edited but does not re-render your component
After 2 seconds, setState is called, updating the state state
At this point, a value has changed for a new one because your component is re-rendered after a state update. state also changed for what you gave to it
So for your question :
if the state (i.e. someState) or props get updated, does it run through the function again, making a undefined? Does only JSX elements that depend on the state/props and the hooks that use them get affected? What changes exactly?
The answer is yes, it does run through the function again, and the state/props that has been updated are updated in the JSX. Local variable like a will be set to their default value.
Related
React throw an error when we try to update the state on an unmounted component.So When I test react component for that I am getting errors on the first render only.
I made a component that enable child component based on click. And child component have button which updates state after some settimeout which throw react warning
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.
Which is perfectly valid. But to overcome this I am passing enable props from the parent component based on that there is a condition just before setTimeout in the child component. So why does it throw an error the first time only?
To reproduce
Click on the child component button which is false and click on parent component button enable which unmount the child component.
**My question is why react throw an error on the first time only ? And why it is working fine on second time **
Parent component
import { useState } from "react";
import "./styles.css";
import { Test } from "./Test";
export default function App() {
const [state, setstate] = useState(true);
const changeState = () => {
setstate(!state);
};
return (
<div className="App">
<button onClick={changeState}>enable </button>
{state && <Test enable={state} />}
</div>
);
}
Child Component
import React, { useState } from "react";
export const Test = (props) => {
const [state, setstate] = useState(false);
const fetchData = () => {
setstate(!state);
if (props.enable) {
setTimeout(() => {
setstate(false);
}, 1000);
}
};
return (
<>
<button onClick={fetchData}> {`${state}`}</button>
</>
);
};
Codesandbox link to test
Nice track, Just you are missing a minor point, when you write a state thats needed some time to execute and the same time we can visit the flow again and again base on any action, then we need to clear old subscription before go to new one...
For example, in your code here, you update state flow, but the state flow is register a new subscription every time we visit a component with valid props and click on button, so that, prev execution may still work when you trigger new event, so simply, what we need to do unmounted old subscription and we can do that by this for your case:
import React, { useState, useEffect } from "react";
export const Test = (props) => {
const [state, setstate] = useState(false);
useEffect(() => {
if (props.enable) {
const timer = setTimeout(() => {
setstate(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [state, props.enable]);
const fetchData = () => {
setstate((prev) => !prev);
};
return (
<>
<button onClick={fetchData}> {`${state}`}</button>
</>
);
};
Look at code above, simply we add code need to cleanup in effect which its look to my state and prop, now when I click on button, the effect will trigger, if we do that again, the clearTime will work for prev subscription and then add new one and so on...
Notes:
In your case we can remove function and use setState direct on your button.
Prefer to use useCallBack in your function like const fetchData = useCAllback...
You can use setstate((prev) => !prev); its will be work as snapshot, and its usefull when you depends on old value..but may it not needed in some cases too, but just to know about this feature.
Update 1:
What is Subscription:
You can say the subscription represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription, in another word, you can say yes, any async task or any job will be invoke to react life-cycle state and its needed to observe changes, then you talk about subscribe, like API or time out or time interval and so on, any of these action thats need to clear prev subscribe (stop observer - unsubscribe) to prevent any memory leek and clear memory to keep state flow safe and prevent unneeded reredner.
I'm trying to understand what happens when you have both props and useState in one component.
I wrote little example of it which has one parent component that prints its numbers with another child component -
const MyNumbers = (props) => {
const [numbers, setNumbers] = useState([...props.arr]);
function changeNumbers() {
setNumbers((nums) => [...nums.map(() => Math.floor(Math.random() * 10))]);
}
return (
<div className="MyNumbers">
<div>
<button onClick={changeNumbers}>Chane numbers</button>
</div>
<div>
{numbers.map((num, idx) => (
<SingleNumber key={idx} num={num}></SingleNumber>
))}
</div>
</div>
);
};
const SingleNumber = (props) => {
const [num] = useState(props.num);
useEffect(() => {
console.log("useEffect called");
});
return <h3>The number is {num}</h3>;
};
Here is the above demo
The SingleNumber component uses useState and as you can see clicking on the "Change numbers" action doesn't change the values in the children component.
But when I wrote almost the same code but now SingleNumber doesn't use useState then clicking on the "Change numbers" changes all the values in the children component (like in this demo).
Is it correct to say that a function component with a useState renders once and then only changed if the state changed but not if the props changed ?
OFC the component "rerenders" when the props change, the useEffect hook in SingleNumber is showing you that the "render phase" is run each time the props change.... effects are run each time the component is rendered.
const SingleNumber = (props) => {
const [num] = useState(props.num);
useEffect(() => {
console.log("useEffect called"); // <-- logged each time the component renders
});
return <h3>The number is {num}</h3>;
};
If you added a dependency on props.num and updated the local state (don't actually do this, it's an anti-pattern in react!), you'll see the UI again update each time the props update.
To answer your queston:
Is it correct to say that a function component with a useState renders
once and then only changed if the state changed but not if the props
changed?
No, this is not technically correct to say if "render" to you means strictly react rendered the component to compute a diff, react components rerender when state or props update. Yes, if "render" more generally means you visually see the UI update.
When you call useState it returns an array with two values in it:
The current value of that bit of the state
A function to update the state
If there is no current value when it sets the state to the default value and returns that.
(The default value is the argument you pass to useState).
If you change the values of props in your example, then the component rerenders.
useState returns the current value of that bit of the state. The state has a value, so it doesn't do anything with the argument you pass to useState. It doesn't matter that that value has changed.
Since nothing else has changed in the output, the rerendered component doesn't update the DOM.
Is it correct to say that a function component with a useState renders once and then only changed if the state changed but not if the props changed?
No, it does rerender but doesn't commit the changes.
When parent component MyNumbers re-renders by clicking changeNumbers, by default (unless React.memo used) all its children components (like SingleNumber) will be re-render.
Now when SingleNumber rerenders, notice useState docs.
During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).
You initial the state useState(props.num) but it can only be changed by calling the setter function, therefore the state num won't change because you never calling the setter.
But it will rerender on parent render as mentioned above (notice the useEffect logs).
You don't need to use useState in SingleNumber.
because useState called only once when it rendered.
const SingleNumber = (props) => {
// const [num] = useState(props.num);
// useEffect(() => {
// console.log("useEffect called");
// });
return <h3>The number is {props.num}</h3>;
};
if you want to use useState, you can use like this.
const SingleNumber = (props) => {
const [num, setNum] = useState(props.num);
useEffect(() => {
console.log("useEffect called");
setNum(props.num);
}, [props.num]);
return <h3>The number is {num}</h3>;
};
My code has a component that takes both props and has its own internal state.
The component should rerender ONLY when its props change. State changes should NOT trigger a rerender.
This behaviour can be implemented with a class based component and a custom shouldComponentUpdate function.
However, this would be the first class based component in the codebase. Everything is done with functional components and hooks.
Therefore I would like to know whether it is possible to code the desired functionality with functional components.
After a few answers that didn't approach the real problem, I think I have to reformulate my question. Here is a minimal example with two components:
Inner takes a prop and has state. This is the component in question. It must not rerender after state changes. Prop changes should trigger a rerender.
Outer is a wrapper around inner. It has no meaning in the scope of this question and is only there to give props to Inner and to simulate prop changes.
To demonstrate the desired functionality I have implemented Inner with a class based component. A live version of this code can be found on codesandbox. How can I migrate it to a functional component:
Inner.tsx:
import React, { Component } from 'react'
interface InnerProps{outerNum:number}
interface InnerState{innerNum:number}
export default class Inner extends Component<InnerProps, InnerState> {
state = {innerNum:0};
shouldComponentUpdate(nextProps:InnerProps, nextState:InnerState){
return this.props != nextProps;
}
render() {
return (
<button onClick={()=>{
this.setState({innerNum: Math.floor(Math.random()*10)})
}}>
{`${this.props.outerNum}, ${this.state.innerNum}`}
</button>
)
}
}
Outer.tsx:
import React, { useState } from "react";
import Inner from "./Inner";
export default function Outer() {
const [outerState, setOuterState] = useState(1);
return (
<>
<button
onClick={() => {
setOuterState(Math.floor(Math.random() * 10));
}}
>
change outer state
</button>
<Inner outerNum={outerState}></Inner>
</>
);
}
The official docs say to wrap the component in React.memo. But this doesn't seem to work for preventing rerenders on state change. It only applies to prop changes.
I have tried to make React.memo work. You can see a version of the code with both Outer and Inner being functional components here.
Related questions:
How to use shouldComponentUpdate with React Hooks? : This question only deals with prop changes. The accepted answer advises to use React.memo
shouldComponentUpdate in function components : This question predates stateful functional components. The accepted answer explains how functional components don't need shouldComponentUpdate since they are stateless.
React memo do not stop state changes
React.memo only checks for prop changes. If your function component
wrapped in React.memo has a useState or useContext Hook in its
implementation, it will still rerender when state or context change.
Ref:- https://reactjs.org/docs/react-api.html#reactmemo
Your Inner component depends on the property num of the Outer component, you can't prevent it from rendering on property change as React.memo makes properties comparison:
// The default behaviour is shallow comparison between previous and current render properties.
const areEqual = (a, b) => a.num === b.num;
export default React.memo(Inner, areEqual);
By memoizing the Inner component and removing the num dependency, it won't render on Outer rendering, see sandbox attached.
export default function Outer() {
const [outerState, setOuterState] = useState(1);
return (
<>
...
// v Inner is memoized and won't render on `outerState` change.
<Inner />
</>
);
}
If you want to implement shouldComponentUpdate with hooks you can try:
const [currState] = useState();
// shouldUpdateState your's custom function to compare and decide if update state needed
setState(prevState => {
if(shouldUpdateState(prevState,currState)) {
return currState;
}
return prevState;
});
React is by design driven by setState -> re-render loop. Props change is in fact a setState somewhere in parent components. If you don't want the setState to trigger a re-render, then why in the first place use it?
You can pull in a const state = useRef({}).current to store your internal state instead.
function InnerFunc(props) {
const state = useRef({ innerNum: 0 }).current;
return (
<button
onClick={() => {
state.innerNum = Math.floor(Math.random() * 10);
}}
>
{`${props.outerNum}, ${state.innerNum}`}
</button>
);
}
That said, it's still a valid question to ask: "how to implement shouldComponentUpdate in a react hook fashion?" Here's the solution:
function shouldComponentUpdate(elements, predicate, deps) {
const store = useRef({ deps: [], elements }).current
const shouldUpdate = predicate(store.deps)
if (shouldUpdate) {
store.elements = elements
}
store.deps = deps
return store.elements
}
// Usage:
function InnerFunc(props) {
const [state, setState] = useState({ innerNum: 0 })
const elements = (
<button
onClick={() => {
setState({ innerNum: Math.floor(Math.random() * 10) });
}}
>
{`${props.outerNum}, ${state.innerNum}`}
</button>
);
return shouldComponentUpdate(elements, (prevDeps) => {
return prevDeps[0] !== props
}, [props, state])
}
Noted that it's impossible to prevent a re-render cycle when setState is called, the above hook merely makes sure the re-rendered result stays the same as prev rendered result.
you should use the event that provide the browser and capture in the function before setState, like this
function setState = (e) =>{ //the e is the event that give you the browser
//changing the state
e.preventDefault();
}
I have got an hook who catch getBoundingClientRect object of a ref DOM element. The problem is, at the first render, it return null and I need to get the value only on first render on my component.
I use it like that in a functional component:
const App = () => {
// create ref
const rootRef = useRef(null);
// get Client Rect of rootRef
const refRect = useBoundingClientRect(rootRef);
useEffect(()=> {
// return "null" the first time
// return "DOMRect" when refRect is update
console.log(refRect)
}, [refRect])
return <div ref={rootRef} >App</div>
}
Here the useBoundingClientRect hook, I call in App Component.
export function useBoundingClientRect(pRef) {
const getBoundingClientRect = useCallback(() => {
return pRef && pRef.current && pRef.current.getBoundingClientRect();
}, [pRef]);
const [rect, setRect] = useState(null);
useEffect(() => {
setRect(getBoundingClientRect());
},[]);
return rect;
}
The problem is I would like to cache boundingClientRect object on init and not the second time component is rerender :
// App Component
useEffect(()=> {
// I would like to get boundingClientRect the 1st time useEffect is call.
console.log(refRect)
// empty array allow to not re-execute the code in this useEffect
}, [])
I've check few tutorials and documentations and finds some people use useRef instead of useState hook to keep value. So I tried to use it in my useboundingClientRect hook to catch and return the boundingClientRect value on the first render of my App component. And it works... partially:
export function useBoundingClientRect(pRef) {
const getBoundingClientRect = useCallback(() => {
return pRef && pRef.current && pRef.current.getBoundingClientRect();
}, [pRef]);
const [rect, setRect] = useState(null);
// create a new ref
const rectRef = useRef(null)
useEffect(() => {
setRect(getBoundingClientRect());
// set value in ref
const rectRef = getBoundingClientRect()
},[]);
// return rectRef for the first time
return rect === null ? rectRef : rect;
}
Now the console.log(rectRef) in App Component allow to access the value on first render:
// App Component
useEffect(()=> {
console.log(refRect.current)
}, [])
But If I try to return refRect.current from useBoundingClientRect hook return null. (What?!)
if anyone can explain theses mistakes to me. Thanks in advance!
You need to understand references, mututation, and the asynchronous nature of updates here.
Firstly, when you use state to store the clientRect properties when your custom hook in useEffect runs, it sets value in state which will reflect in the next render cycle since state updates are asynchronous. This is why on first render you see undefined.
Secondly, when you are returning rectRef, you are essentially returning an object in which you later mutate when the useEffect in useBoundingClientRect runs. The data is returned before the useEffect is ran as it runs after the render cycle. Now when useEffect within the component runs, which is after the useEffect within the custom hook runs, the data is already there and has been updated at its reference by the previous useEffect and hence you see the correct data.
Lastly, if you return rectRef.current which is now a immutable value, the custom hook updates the value but at a new reference since the previous one was null and hence you don't see the change in your components useEffect method.
I have a higher order functional component in my app that passes JSON to it's child component. I am using react Hooks to manage state. I can't use componentDidUpdate since this is not a class component.
I'm using useEffect() to get it to process the JSON on initial render, but after that I can't seem to get it to update. I've confirmed the PROPS are indeed changing and the JSON is unique (it changes on the click of a button).
Here is my code:
function FormSection(props) {
const [questions, updateQuestions] = useState([]);
const [sectionJSON, updateSectionJSON] = useState(props.json);
const fetchQuestionsData = json => {
/* API CALL TRANSFORM JSON INTO HTML FOR USE ELSEWHERE IN THE APP */
};
useEffect(() => {
fetchQuestionsData(sectionJSON);
}, [sectionJSON]);
...
}
I've also tried changing the useEffect() hook to use props directly:
useEffect(() => {
fetchQuestionsData(props.json);
}, [props.json]);
The parent componenet is calling it as follows:
<FormSection key={i} json={newJSON} />
Is there something obvious I am missing?
When you set,
const [sectionJSON, updateSectionJSON] = useState(props.json);
is equivalent to,
constructor(props){
super(props);
this.state = {
sectionJSON: props.json
}
}
So your sectionJSON will set only once and any subsequent change in props.json will not change sectionJSON.
So this will execute only once, and not when props.json changes
useEffect(() => {
fetchQuestionsData(sectionJSON);
}, [sectionJSON]);
To make this work you need to pass props.json to your useEffect hook as,
useEffect(() => {
fetchQuestionsData(props.json);
}, [props.json]);
Doing this will not re-render your component, because your component's state is not changing, like we do using componentDidUpdate in class based component, so you should set your state in fetchQuestionsData like,
const fetchQuestionsData = json => {
/* API CALL TRANSFORM JSON INTO HTML FOR USE ELSEWHERE IN THE APP */
updateSectionJSON(json); //This will re-render your component.
};