React JS re-rendering - javascript

When a component re-renders as a result of calling the state setter function returned by useState, does the entire function component get called again or only parts of the function component get called?
function MyComponent() {
let [count, setCount] = useState(0);
const handleChange = () {
setCount((prevCount) => prevCount+1)
}
return (
<p onClick={handleChange}>
Count = {count}
</p>
)
}
In the above example, when we click the paragraph element, handleChange() gets called which in turn calls the state setter function setCount(). This causes our component to re-render. But when the component re-renders( or the function MyComponent gets called), the useState(0) function gets called again. This should set the initial value of count to 0 again. So shouldn't the counter be stuck at 0 instead of progressing to 1, 2, 3 and so on?

Whole functional component will be rerendered. If the props value which you are passing to the child component is not changing then you can prevent the rerendering of child component by using React.memo

Let say your component tree has parent and has children called childOne and childtwo.
if you change the state of the ChildOne;Only the ChildOne get rendered, Not Parent and childtwo get rendered. But if you change the state of the parent, whole component tree get retendered

This is the idea of hooks. They use closures.
If it was let count = 0, then yes, if would reset to 0, this is why it is let [count] = useState(0)
The whole thing gets re-render and everything is getting re-calculated.
The argument you pass in useState / useRef is used only once, when the component mounts. If it is expensive operation, it recalculate on each render (there are ways to improve this), but the value of it is used only once. Then when you call setState it updates the value.
They work like this:
let ref = {};
const useRef = (defaultValue) => {
if (!ref.current) {
ref.current = defaultValue
}
return ref
}
The hook argument is used only once.

Related

Updating state to the same state directly in the component body

Let's say I have this simple dummy component:
const Component = () => {
const [state, setState] = useState(1);
setState(1);
return <div>Component</div>
}
In this code, I update the state to the same value as before directly in the component body. But, this causes too many re-renders even if the value stayed the same.
And as I know, in React.useState, if a state value was updated to the same value as before - React won't re-render the component. So why is it happening here?
However, if I try to do something simillar with useEffect and not directly in the component body:
const Component = () => {
const [state, setState] = useState(1);
useEffect(()=>{
setState(1);
},[state])
return <div>Component</div>
}
This is not causing any infinte loop and goes exactly according to the rule that React won't re-render the component if the state stayed the same.
So my question is: Why is it causing an infinte loop when I do it directly in the component body and in the useEffect it doesn't?
If someone has some "behind the sences" explanation for this, I would be very grateful!
TL;DR
The first example is an unintentional side-effect and will trigger rerenders unconditionally while the second is an intentional side-effect and allows the React component lifecycle to function as expected.
Answer
I think you are conflating the "Render phase" of the component lifecycle when React invokes the component's render method to compute the diff for the next render cycle with what we commonly refer to as the "render cycle" during the "Commit phase" when React has updated the DOM.
See the component lifecycle diagram:
Note that in React function components that the entire function body is the "render" method, the function's return value is what we want flushed, or committed, to the DOM. As we all should know by now, the "render" method of a React component is to be considered a pure function without side-effects. In other words, the rendered result is a pure function of state and props.
In the first example the enqueued state update is an unintentional side-effect that is invoked outside the normal component lifecycle (i.e. mount, update, unmount).
const Component = () => {
const [state, setState] = useState(1);
setState(1); // <-- unintentional side-effect
return <div>Component</div>;
};
It's triggering a rerender during the "Render phase". The React component never got a chance to complete a render cycle so there's nothing to "diff" against or bail out of, thus the render loop occurs.
The other example the enqueued state update is an intentional side-effect. The useEffect hook runs at the end of the render cycle after the next UI change is flushed, or committed, to the DOM.
const Component = () => {
const [state, setState] = useState(1);
useEffect(() => {
setState(1); // <-- intentional side-effect
}, [state]);
return <div>Component</div>;
}
The useEffect hook is roughly the function component equivalent to the class component's componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. It is guaranteed to run at least once when the component mounts regardless of dependencies. The effect will run once and enqueue a state update. React will "see" that the enqueued value is the same as the current state value and won't trigger a rerender.
Similarly you could use the useEffect hook and completely remove the dependency array so it's an effect that would/could fire each and every render cycle.
const Component = () => {
const [state, setState] = useState(1);
useEffect(() => {
setState(1);
});
return <div>Component</div>;
}
Again, the useEffect hook callback is guaranteed to be invoked at least once, enqueueing a state update. React will "see" the enqueued value is the same as the current state value and won't trigger a rerender.
The takeaway here is to not code unintentional and unexpected side-effects into your React components as this results in and/or leads to buggy code.
When invoking setState(1) you also trigger a re-render since that is inherently how hooks work. Here's a great explanation of the underlying mechanics:
How does React.useState triggers re-render?

setting state with useState affects all useState hooks

I am trying to build an ecommerce website, and I hit a problem I cannot seem to resolve. I am very new to react and JS so have some patience please :)
I declared 4 useStates in my app.js:
const [elementeDinState, setElementeDinState] = useState([]);
const [currentCategorie, setCurrentCategorie] = useState("Acasa");
const [subCategorie, setSubcategorie] = useState([]);
const [cartContents, setCartContents] = useState([]);
const fetchData = useCallback(async () => {
const data = await getCategories();
setElementeDinState(data);
}, []);
useEffect(() => {
fetchData().catch(console.error);
}, [fetchData]);
const changeHeader = (dataFromMenuItem) => {
setCurrentCategorie(dataFromMenuItem);
};
const changeCopiiContent = (data1FromThere) => {
setSubcategorie(data1FromThere);
};
const changeCart = (dataFromCart) => {
setCartContents(dataFromCart);
};
I am passing the functions to change those states to different child components as props. my problem is, when I add items to cart it triggers a re render of my component (products listing component) that should not be affected by cartContents and that resets the state of said component to the initial value that changes the items being shown. does useState hook create a single global state comprised of all those states?
If these useState are defined in the app.js and then passed down, when a child will use them chasing the state will happen in the app.js so all the children of <App /> will be re-rendered.
I guess that your app.js looks similar:
function App() {
const [elementeDinState, setElementeDinState] = useState([]);
// ...and the other hooks and methods
return (
<cartContents setElementDinState={setElementeDinState} />
<ProductList />
)
}
In this case the state is in the component so when <CartContents /> changes it, it will trigger a re-render of the and all its children <ProductList /> included.
To avoid this problem think better when each piece of state needs to be and put the state as near as possibile to that component. For example, if the state of the cart does not influence the Product list. Move the useState in the <Cart /> component.
From what I understand, your problem is that you're simply resetting the cartContents state every time you call the changeCart function, correct?
What you probably want, is to add (or remove ?) the item to the cart, like this?
const changeCart = (dataFromCart) => {
setCartContents(oldContents => [...oldContents, dataFromCart]);
};
Here is a description of useState from the oficial site:
useState is a Hook (...). We call it inside a function component to add some local state to it
So it creates just a local state.
About your problem, We need more information, but I believe that some parent component of that widget is trying to render other component instead of your the component that you wanted (let's call it "ProblemComponent") and rendering you ProblemComponent from scratch again, before you can see it.
it's something like that:
function ParentComponent(props: any) {
const isLoading = useState(false);
// Some logic...
if(isLoading) {
return <LoadingComponent/>;
}
return <ProblemComponent/>;
}
If that doesn't work you can also try to use React.memo() to prevent the ProblemComponent to update when it props change.
well, seems like I wanted to change the way react works so I figured out a work around, based on what you guys told me. I declared the state of the productsComponent in the parent component and adding to cart now doesn't force a refresh of the items being shown. thank you!

Calling function directly in functional component body in React.js

I have a functional component and some function is called directly in the body (that function changes the state && it's needed to be run each render). Is it 100% okay, or could it cause bugs?
const myFunc = (setMyState, ...) => {
...
setMyState(...)
}
const MyComponent = () => {
const [myState, setMyState] = useState(...)
myFunc(setMyState, ...)
return(<div>some content...</div>)
}
This will cause an infinite loop. This is why:
component renders first time
function updates component state
component renders because of state update
function runs again and updates component again
etc
You can definitely avoid this by using if-statements inside the function, but the best way to update a component state is either by user input/api calls or props updating(your case I suppose)
The best way to do that in my opinion is using the useEffect hook

Explanation: Get previous state

I'm reading the React Documentation and there is a topic that I didn't understand very well:
https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
Demo:
https://codesandbox.io/s/get-previous-state-ref--hook-faq-w0f3k?file=/src/App.js
I'd like a better explanation about this. Kind, how it's works?. Thanks
There's three pieces here. A state, a ref, and an effect.
A state is a value that is remembered between renders, and it re-renders it's component when changed.
A reference (or ref) is a value that persists between renders like state does, but unlike state it's not watched for any changes and can be mutated freely. Changes to refs never cause renders.
An effect is simply a function that runs after a render, or after a render in one of its dependencies has changed since the previous render.
So putting that together here's what happens:
const [count, setCount] = useState(0);
Here a state is declared. Calling setCount with a new value will cause the component to render, and when it does, count will have the value that was set.
const prevCountRef = useRef();
Then declare the ref. This will hold a reference to the previous count value. Right now it no value (undefined), which makes sense considering there is no previous value when the component first renders.
useEffect(() => {
prevCountRef.current = count;
});
Now declare an effect which runs after every render (you can tell this because it has no dependencies (would be the second argument to useEffect()). When that runs it sets the current value of prevCountRef to whatever the count is.
So, on the first render, in pseudo code:
// render starts
count // 0
prevCountRef.current // undefined
// render finishes
effect runs {
prevCountRef.current is set to 0
}
So when the render is happening the count state is 0 and the previous count ref is undefined.
Second render:
setCount(1) // triggers the second render
// second render starts
count // 1
prevCount.current // 0
// second render finishes
effect runs {
prevCount.current is set to 1
}
So as you can see, during the render you always have the current count as as state, and the previous count as the ref. This works because the effect that sets the refs value always runs after the render is done, which allows it to be whatever the previous value was.
You can use componentDidUpdate to get the prev props and state. When you extend React.Component, it will call componentDidUpdate after each render. You define the function, and React will pass the prev props and prev state to you.
https://reactjs.org/docs/react-component.html#componentdidupdate
useEffect is called just after the component render.
The first time the component render prevCount is undefined because useEffect has not been called yet.
pre-render:
1) value: 0, prevCount: undefined
post-render:
useEffect is called:
2) value: 0, prevCount: 0 useRef don't cause a new rendering, thus the component is not updated. refCount will be shown as undefined.
when you change the count value the component gets re-render:
pre-render:
3) value: 1, prevCount: 0 useEffect is not called yet.
post-render:
4) value: 1, prevCount: 1 useEffect is called, prevCount increased but the component is not updated so prevCount will be shown the the value = 0;

Changing React state happens a re-render later, then it should

I have a functional component managing several states, amongst other things a state, which stores the index, with which I am rendering a type of table, when clicking a suitable button. OnClick that button calls a callback function, which runs a click handler. That click handler changes the index state, to the same 'index' as the array entry, in which I store an object with information for the rendering of a child component.
I would expect, that onClick the state would change before the rendering happens, so the component could render correctly. Yet it only happens a render later.
I already tried calling the useEffect-hook, to re-render, when that index state changes, but that didn't help neither.
Here is a shortened version of the code:
export const someComponent = () => {
[index, setIndex] = useState(-1);
const handleClick = (id) => {
setIndex(id);
// This is a function, I use to render the table
buildNewComponent(index);
}
}
Further 'down' in the code, I got the function, which is rendering the table entries. There I pass the onClick prop in the child component of the table as following:
<SomeEntryComponent
onClick={() => handleClick(arrayEntry.id)}
>
// some code which is not affecting my problem
</SomeEntryComponent>
So as told: when that onClick fires, it first renders the component when one presses it the second time, because first then the state changes.
Could anyone tell me why that happens like that and how I could fix it to work properly?
As other have stated, it is a synchronicity issue, where index is being updated after buildComponent has been invoked.
But from a design standpoint, it would be better to assert index existence by its value, as opposed to flagging it in a handler. I don't know the details behind buildComponent, but you can turn it into a conditional render of the component.
Your component rendering becomes derived from its data, as opposed to manual creation.
export const someComponent = () => {
[index, setIndex] = useState(-1);
const handleClick = (id) => setIndex(id);
const indexHasBeenSelected = index !== -1
return (
<div>
{indexHasBeenSelected && <NewComponent index={index} />}
</div>
)
}
When calling buildNewComponent the index is not yet updated. You just called setState, there is no guarantee that the value is updated immediately after that. You could use id here or call buildNewComponent within a useEffect that has index as its dependency.
I believe that you can have the correct behavior if you use useEffect and monitor index changes.
export const someComponent = () => {
[index, setIndex] = useState(-1);
useEffect(() => {
// This is a function, I use to render the table
buildNewComponent(index);
}, [buildNewComponent, index])
const handleClick = (id) => {
setIndex(id);
}
}
The process will be:
When the use clicks and call handleClick it will dispatch setIndex with a new id
The index will change to the same id
The useEffect will see the change in index and will call buildNewComponent.
One important thing is to wrap buildNewComponent with useCallback to avoid unexpected behavior.

Categories