Why does React re-renders children when props does not change? - javascript

Consider the following example:
import "./styles.css";
import React from "react";
function Children(props) {
const counter = props.counter2;
console.log("re-rendering children");
return (
<div className="App">
<p>counter2 value = {counter} </p>
</div>
);
}
export default function App() {
const [counter, setCounter] = React.useState(0);
const [counter2] = React.useState(0);
return (
<div className="App">
<p>counter value = {counter} </p>
<button onClick={(e) => setCounter((c) => c + 1)}>increment</button>
<Children counter2={counter2} />
</div>
);
}
The Children component does not depend on the parent state counter, but when I click the button I can see that re-rendering log printed every time I click. If I am not mistaken then it means the Children component is re-rendered. My question is why? It should not re-render as the props did not change.
code sandbox: https://codesandbox.io/s/modern-dawn-xj9b8

Because of the lifecycle of React, when you are using a functional component, it is also considered stateless and will re-render every time there is an update in its parent component, whether there is a change in props passing into the child.
There are two ways you can do to change this behaviour,
use class component and decide whether to update it using shouldComponentUpdate
or the recommended way using React.memo,
import "./styles.css";
import React from "react";
function Children(props) {
const counter = props.counter2;
console.log("re-rendering children");
return (
<div className="App">
<p>counter2 value = {counter} </p>
</div>
);
}
const MemoChild = React.memo(Children);
export default function App() {
const [counter, setCounter] = React.useState(0);
const [counter2] = React.useState(0);
return (
<div className="App">
<p>counter value = {counter} </p>
<button onClick={(e) => setCounter((c) => c + 1)}>increment</button>
<MemoChild counter2={counter2} />
{/* <Children counter2={counter2} /> */}
</div>
);
}
More on React.memo in the docs
If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.

this answer is true that you can use React.memo but before start using, check some articles to figure out when it's valuable to use it.
using it inappropriate could damage your performance.

Related

When I am using onChage here, it takes only second change. The first change I've tried in the input is not taking

Why the input only taking inputs from second input only?
import React, { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
let toDoIs = document.getElementById("toDoInput");
const [ToDo, setToDoIs] = useState("d");
const [ToDoArray, setToDoArray] = useState([]);
return (
<div>
<h1>ToDo</h1>
<input
id="toDoInput"
onChange={() => {
setToDoIs(toDoIs.value);
}}
type="text"
/>
<button
onClick={() => {
setToDoArray([...ToDoArray, { text: ToDo }]);
toDoIs.value = "";
}}
>
Add
</button>
<Item push={ToDoArray} />
</div>
);
}
export default ToDo;
Why the second input only works, which means whenever I use submit the value from second input only stored and displayed. I don't know why this happens.
There's a few problems here...
Don't use DOM methods in React. Use state to drive the way your component renders
Your text input should be a controlled component
When updating state based on the current value, make sure you use functional updates
import { useState } from "react";
import Item from "./Components/Item";
import "./ToDo.css";
function ToDo() {
// naming conventions for state typically use camel-case, not Pascal
const [toDo, setToDo] = useState("d");
const [toDoArray, setToDoArray] = useState([]);
const handleClick = () => {
// use functional update
setToDoArray((prev) => [...prev, { text: toDo }]);
// clear the `toDo` state via its setter
setToDo("");
};
return (
<div>
<h1>ToDo</h1>
{/* this is a controlled component */}
<input value={toDo} onChange={(e) => setToDo(e.target.value)} />
<button type="button" onClick={handleClick}>
Add
</button>
<Item push={toDoArray} />
</div>
);
}
export default ToDo;

React's Context API re-renders all components that are wrapped inside the Context

This is a very common performance problem while using the Context API. Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown.
If I have a the wrapper as this:
<CounterProvider>
<SayHello />
<ShowResult />
<IncrementCounter />
<DecrementCounter />
</CounterProvider>
And the value props as:
<CounterContext.Provider value={{increment, decrement, counter, hello }} >
{children}
</CounterContext.Provider>
Everytime I increment the count value from the IncrementCounter component, the entire set of wrapped components re-renders as it is how the Context API is supposed to work.
I did a bit of research and came across these solutions:
Split the Context into N number of Context according to the use-case : This solution works as expected.
Wrap the value provider using React.Memo: I saw a lot of articles suggesting to the React.Memo API as follows:
<CounterContext.Provider
value={useMemo(
() => ({ increment, decrement, counter, hello }),
[increment, decrement, counter, hello]
)}
>
{children}
</CounterContext.Provider>
This however doesn't work as expected. I still can see all the components getting re-rendered. What I'm doing wrong while using the Memo API? Dan Abramov does recommend to go by this approach in an open React issue
If anyone can help me out on this one. Thanks for reading.
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The above statement is true if a context is used like in the below example where components are directly nested in the provider. All of them re-render when count changes, no matter wether they are called useContext(counterContext) or not.
const counterContext = React.createContext();
const CounterContextProvider = () => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
<ComponentOne/>
<ComponentTwo />
</counterContext.Provider>
);
};
const ComponentOne = () => {
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider/>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The statement is false if you are consuming nested components with children. This time when count changes CounterContextProvider renders, but since it's rendering because its state has changed and not because of its parent rendering, and because a component cannot mutate its props, React won't render children. That's it if it was a normal component.
But since there is a context involved here, React will find all components that contain useContext(counterContext) and render them.
const counterContext = React.createContext();
const CounterContextProvider = ({ children }) => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
{children}
</counterContext.Provider>
);
};
const ComponentOne = () => {
const { count } = React.useContext(counterContext);
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider>
<ComponentOne />
<ComponentTwo />
</CounterContextProvider>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In the above example only ComponentOne renders when count changes, which is normal cause he is consuming it. Every component that calls useContext(counterContext) renders if one value of the context changes.
Even with useMemo wrapping the context object as you did, that's the behavior you get as soon as one variable in its dependency array changes.

Can't pass useState() 'set' function to grand child

I'm having issues trying to get my useState variable to work. I create the state in my grandparent then pass it into my parent. Here's a simplified version of my code:
export function Grandparent(){
return(
<div>
const [selectedID, setSelectedID] = useState("0")
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
Parent:
const Parent = ({setSelectedID2 ...}) => {
return(
<div>
{setSelectedID2("5")} //works
<Child setSelectedID3={setSelectedID2} />
</div>
)
}
From the parent I can use 'setSelectedID2' like a function and can change the state. However, when I try to use it in the child component below I get an error stating 'setSelectedID3' is not a function. I'm pretty new to react so I'm not sure if I'm completely missing something. Why can I use the 'set' function in parent but not child when they're getting passed the same way?
Child:
const Child = ({setSelectedID3 ...}) => {
return(
<div >
{setSelectedID3("10")} //results in error
</div>
);
};
In React you make your calculations within the components/functions (it's the js part) and then what you return from them is JSX (it's the html part).
export function Grandparent(){
const [selectedID, setSelectedID] = useState("0");
return(
<div>
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
You can also use (but not define!) some js variables in JSX, as long as they are "renderable" by JSX (they are not Objects - look for React console warnings).
That's your React.101 :)
Here's a working example with everything you have listed here. Props are passed and the function is called in each.
You don't need to name your props 1,2,3.., they are scoped to the function so it's fine if they are the same.
I moved useState and function calls above the return statement, because that's where that logic should go in a component. The jsx is only used for logic dealing with your display/output.
https://codesandbox.io/s/stupefied-tree-uiqw5?file=/src/App.js
Also, I created a working example with a onClick since that's what you will be doing.
https://codesandbox.io/s/compassionate-violet-dt897?file=/src/App.js
import React, { useState } from "react";
export default function App() {
return <Grandparent />;
}
const Grandparent = () => {
const [selectedID, setSelectedID] = useState("0");
return (
<div>
{selectedID}
<Parent setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Parent = ({ selectedID, setSelectedID }) => {
setSelectedID("5");
return (
<div>
{selectedID}
<Child setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Child = ({ selectedID, setSelectedID }) => {
setSelectedID("10");
return <div>{selectedID}</div>;
};
output
10
10
10
const [selectedID, setSelectedID] = useState("0")
should be outside return

return component with hook in react?

I have 2 components. I want to keep component A clean, hence I don't wish to use useState in it, I wish to use useState in component B, but since the component B isn't a hook (because it return the jsx as well), how can I call stateHandler in component A?
const ComponentA = () => {
return (
<div>
<ComponentB />
<button onClick={()=>{
//what to do here to control the state of component B?
}}>show component B</button>
</div>
)
}
const ComponentB = () => {
//I don't want to move this setShowBlock to index.js for clean code purpose
const [showBlock, setShowBlock] = useState(false)
return (
<div>
{showBlock && <div>component B</div>}
</div>
)
}
https://stackblitz.com/edit/react-ggtt87?file=index.js
you can use context as central store,
in the context you create the useState hook with showBlock and setShowBlock and then you can change it from component A
const ComponentA = () => {
const {setShowBlock} = useContext(MyContext)
return (
<div>
<ComponentB />
<button onClick={()=>{
here call setShowBlock and change it
}}>show component B</button>
</div>
)
}

React Hooks render twice

I define a scene: we have a component that uses parent's props and itself state.
There are two Components DC and JOKER and my step under the below:
click DC's button
DC setCount
JOKER will render with the old state
running useEffect and setCount
JOKER does render again
I want to ask why JOKER render twice(step 3 and 5) and the first render squanders the performance. I just do not want step 3. If in class component I can use componentShouldUpdate to avoid it. But Hooks has the same something?
My code under the below, or open this website https://jsfiddle.net/stephenkingsley/sw5qnjg7/
import React, { PureComponent, useState, useEffect, } from 'react';
function JOKER(props) {
const [count, setCount] = useState(props.count);
useEffect(() => {
console.log('I am JOKER\'s useEffect--->', props.count);
setCount(props.count);
}, [props.count]);
console.log('I am JOKER\'s render-->', count);
return (
<div>
<p style={{ color: 'red' }}>JOKER: You clicked {count} times</p>
</div>
);
}
function DC() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => {
console.log('\n');
setCount(count + 1);
}}>
Click me
</button>
<JOKER count={count} />
</div>
);
}
ReactDOM.render(<DC />, document.querySelector("#app"))
It's an intentional feature of the StrictMode. This only happens in
development, and helps find accidental side effects put into the
render phase. We only do this for components with Hooks because those
are more likely to accidentally have side effects in the wrong place.
-- gaearon commented on Mar 9, 2019
You can simply make modifications in ./index.js
change this
<React.StrictMode>
<App />
</React.StrictMode>
to this
<>
<App />
</>
React.StrictMode causes component render in development mode. (works in reactjs version 18.0.2)
I'm not sure I understand your question, but here goes.
When your <DC /> component changes state, it passes the new state value count to the component Joker. At this point the component will rerender, accounting for the first change.
Then you bind the effect to props.count changes;
useEffect(() => {
console.log('I am JOKER\'s useEffect--->', props.count);
setCount(props.count);
}, [props.count]);// <-- This one
Which triggers when the component gets the new value from the component DC. It will set the state of it self Joker to props.count, which causes the component to rerender.
Which then gives you the following output:
I am JOKER's render--> 1 // Initial render where Joker receives props from DC
index.js:27 I am JOKER's useEffect---> 2 // The hook runs because props.count changed
index.js:27 I am JOKER's render--> 2 // Joker rerenders because its state updated.
If we just want to do the same something alike componentShouldUpdate, we can use useMemo.
function DC() {
const [count, setCount] = useState(0);
const [sum, setSum] = useState(0);
const memoizedJOKER = useMemo(() => <JOKER count={count} />, [count]);
return (
<div>
<button onClick={() => {
// setCount(count + 1);
setSum(sum + 1);
console.log('---click---');
console.log('\n');
}}>
Click me
</button>
<p>DC: You clicked {count} times</p>
<p>now this is {sum} times</p>
{memoizedJOKER}
</div>
);
}
When you click button, JOKER does not render again.
Use the following custom useEffect code to force react to render a component once, all you need to do is import and use it in place of usEffect.
import {useRef} from 'react'
export const useEffectOnce = ( effect )=> {
const destroyFunc = useRef();
const effectCalled = useRef(false);
const renderAfterCalled = useRef(false);
const [val, setVal] = useState(0);
if (effectCalled.current) {
renderAfterCalled.current = true;
}
useEffect( ()=> {
// only execute the effect first time around
if (!effectCalled.current) {
destroyFunc.current = effect();
effectCalled.current = true;
}
// this forces one render after the effect is run
setVal(val => val + 1);
return ()=> {
// if the comp didn't render since the useEffect was called,
// we know it's the dummy React cycle
if (!renderAfterCalled.current) { return; }
if (destroyFunc.current) { destroyFunc.current(); }
};
}, []);
};
For more information click here

Categories