When to use useImperativeHandle, useLayoutEffect, and useDebugValue - reactjs

I cannot understand why the following useImperativeHandle, useLayoutEffect, and useDebugValue hooks are needed, can you give examples when they can be used, but not examples from the documentation please.

Allow me to preface this answer by stating that all of these hooks are very rarely used. 99% of the time, you won't need these. They are only meant to cover some rare corner-case scenarios.
useImperativeHandle
Usually when you use useRef you are given the instance value of the component the ref is attached to. This allows you to interact with the DOM element directly.
useImperativeHandle is very similar, but it lets you do two things:
It gives you control over the value that is returned. Instead of returning the instance element, you explicitly state what the return value will be (see snippet below).
It allows you to replace native functions (such as blur, focus, etc) with functions of your own, thus allowing side-effects to the normal behavior, or a different behavior altogether. Though, you can call the function whatever you like.
There could be many reasons you want might to do either of the above; you might not want to expose native properties to the parent or maybe you want to change the behavior of a native function. There could be many reasons. However, useImperativeHandle is rarely used.
useImperativeHandle customizes the instance value that is exposed to parent components when using ref
Example
In this example, the value we'll get from the ref will only contain the function blur which we declared in our useImperativeHandle. It will not contain any other properties (I am logging the value to demonstrate this). The function itself is also "customized" to behave differently than what you'd normally expect. Here, it sets document.title and blurs the input when blur is invoked.
const MyInput = React.forwardRef((props, ref) => {
const [val, setVal] = React.useState('');
const inputRef = React.useRef();
React.useImperativeHandle(ref, () => ({
blur: () => {
document.title = val;
inputRef.current.blur();
}
}));
return (
<input
ref={inputRef}
val={val}
onChange={e => setVal(e.target.value)}
{...props}
/>
);
});
const App = () => {
const ref = React.useRef(null);
const onBlur = () => {
console.log(ref.current); // Only contains one property!
ref.current.blur();
};
return <MyInput ref={ref} onBlur={onBlur} />;
};
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
useLayoutEffect
While similar to some extent to useEffect(), it differs in that it will run after React has committed updates to the DOM. Used in rare cases when you need to calculate the distance between elements after an update or do other post-update calculations / side-effects.
The signature is identical to useEffect, but it fires synchronously after all DOM mutations. Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Example
Suppose you have an absolutely positioned element whose height might vary and you want to position another div beneath it. You could use getBoundingClientRect() to calculate the parent's height and top properties and then just apply those to the top property of the child.
Here you would want to use useLayoutEffect rather than useEffect. See why in the examples below:
With useEffect: (notice the jumpy behavior)
const Message = ({boxRef, children}) => {
const msgRef = React.useRef(null);
React.useEffect(() => {
const rect = boxRef.current.getBoundingClientRect();
msgRef.current.style.top = `${rect.height + rect.top}px`;
}, []);
return <span ref={msgRef} className="msg">{children}</span>;
};
const App = () => {
const [show, setShow] = React.useState(false);
const boxRef = React.useRef(null);
return (
<div>
<div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
{show && <Message boxRef={boxRef}>Foo bar baz</Message>}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
.box {
position: absolute;
width: 100px;
height: 100px;
background: green;
color: white;
}
.msg {
position: relative;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
With useLayoutEffect:
const Message = ({boxRef, children}) => {
const msgRef = React.useRef(null);
React.useLayoutEffect(() => {
const rect = boxRef.current.getBoundingClientRect();
msgRef.current.style.top = `${rect.height + rect.top}px`;
}, []);
return <span ref={msgRef} className="msg">{children}</span>;
};
const App = () => {
const [show, setShow] = React.useState(false);
const boxRef = React.useRef(null);
return (
<div>
<div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
{show && <Message boxRef={boxRef}>Foo bar baz</Message>}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
.box {
position: absolute;
width: 100px;
height: 100px;
background: green;
color: white;
}
.msg {
position: relative;
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
useDebugValue
Sometimes you might want to debug certain values or properties, but doing so might require expensive operations which might impact performance.
useDebugValue is only called when the React DevTools are open and the related hook is inspected, preventing any impact on performance.
useDebugValue can be used to display a label for custom hooks in React DevTools.
I have personally never used this hook though. Maybe someone in the comments can give some insight with a good example.

useImperativeHandle
useImperativeHandle allows you to determine which properties will be exposed on a ref. In the example below, we have a button component, and we'd like to expose the someExposedProperty property on that ref:
[index.tsx]
import React, { useRef } from "react";
import { render } from "react-dom";
import Button from "./Button";
import "./styles.css";
function App() {
const buttonRef = useRef(null);
const handleClick = () => {
console.log(Object.keys(buttonRef.current)); // ['someExposedProperty']
console.log("click in index.tsx");
buttonRef.current.someExposedProperty();
};
return (
<div>
<Button onClick={handleClick} ref={buttonRef} />
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
[Button.tsx]
import React, { useRef, useImperativeHandle, forwardRef } from "react";
function Button(props, ref) {
const buttonRef = useRef();
useImperativeHandle(ref, () => ({
someExposedProperty: () => {
console.log(`we're inside the exposed property function!`);
}
}));
return (
<button ref={buttonRef} {...props}>
Button
</button>
);
}
export default forwardRef(Button);
Available here.
useLayoutEffect
This is the same as useEffect, but only fires once all DOM mutations are completed. This article From Kent C. Dodds explains the difference as well as anyone, regarding these two, he says:
99% of the time [useEffect] is what you want to use.
I haven't seen any examples which illustrate this particularly well, and I'm not sure I'd be able to create anything either. It's probably best to say that you ought to only use useLayoutEffect when useEffect has issues.
useDebugValue
I feel like the docs do a pretty good example of explaining this one. If you have a custom hook, and you'd like to label it within React DevTools, then this is what you use.
If you have any specific issues with this then it'd probably be best to either comment or ask another question, because I feel like anything people put here will just be reiterating the docs, at least until we reach a more specific problem.

The useImperativeHandle hook helped me a lot with a use case of mine.
I created a grid component which uses a third-party library component. The library itself has a big data layer with built-in functionality that can be used by accessing the instance of the element.
However, in my own grid component, I want to extend it with methods which perform actions on the grid. Furthermore, I also want to be able to execute those methods from outside of my grid component.
This is easily achievable by adding the methods inside of the useImperativeHandle hook, and then they will be exposed and usable by its parent.
My grid component looks kind of like this:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import ThirdPartyGrid from 'some-library';
export default forwardRef((props, forwardedRef) => {
const gridRef = useRef(null);
useImperativeHandle(forwardedRef,
() => ({
storeExpandedRecords () {
// Some code
},
restoreExpandedRecords () {
// Some code
},
}));
return (
<div ref={forwardedRef}>
<ThirdPartyGrid
ref={gridRef}
{...props.config}
/>
</div>
);
});
And then in my parent, I can execute those methods:
import React, { useRef, useEffect } from 'react';
export default function Parent () {
const gridRef = useRef(null);
const storeRecords = () => {
gridRef.current.storeExpandedRecords();
};
useEffect(() => {
storeRecords();
}, []);
return <GridWrapper ref={gridRef} config={{ something: true }} />
};

useImperativeHandle
usually hook expose your functional based component method and properties to other component by putting functional component inside forwardRef
example
const Sidebar=forwardRef((props,ref)=>{
const [visibility,setVisibility]=useState(null)
const opensideBar=()=>{
setVisibility(!visibility)
}
useImperativeHandle(ref,()=>({
opensideBar:()=>{
set()
}
}))
return(
<Fragment>
<button onClick={opensideBar}>SHOW_SIDEBAR</button>
{visibility==true?(
<aside className="sidebar">
<ul className="list-group ">
<li className=" list-group-item">HOME</li>
<li className=" list-group-item">ABOUT</li>
<li className=" list-group-item">SERVICES</li>
<li className=" list-group-item">CONTACT</li>
<li className=" list-group-item">GALLERY</li>
</ul>
</aside>
):null}
</Fragment>
)
}
//using sidebar component
class Main extends Component{
myRef=createRef();
render(){
return(
<Fragment>
<button onClick={()=>{
///hear we calling sidebar component
this.myRef?.current?.opensideBar()
}}>
Show Sidebar
</button>
<Sidebar ref={this.myRef}/>
</Fragment>
)
}
}

I see that there are already answers to this question. So, I just want to share my articles about useImperativeHandle, useEffect, and useLayoutEffect.
I have written an example-based article aboutuseImperativeHandle hook, you can check it out here: useImperativeHandle by Examples
I also wrote an article about effects in React which explains useEffect hook and useLayoutEffect hook differences in detail: A Beginner’s Guide to Effects in React

Related

How to Add a CSS class to an element onClick with Next.JS

I migrated my app from React to Next Js. The final part I'm confused on is removing/adding classes on click of an element.
Essentially, I have an input element (and some other elements) that is set to display: none by default; on click, it should switch to display: block. This was working in React, but is not working after migration. The actual click event does work when I put an alert() within the function.
PLEASE HELP!!!!
Here is the onClick function:
searchActive = () =>{
if (typeof document !== "undefined"){
const searchForm = document.getElementById(styles["search-form"]);
searchForm.classList.remove(styles["display-none"]);
document.getElementById(styles["search-input-box"]).focus();
document.getElementById(styles["search-icon-id"]).classList.add(styles["display-none"]);
document.getElementById(styles["close-icon-id"]).classList.remove(styles["display-none"]);
}
}
UPDATED: This method of using useState also throws a scope error.
Component Function:
searchActive = () =>{
const [add, setAdd] = useState("none");
useState("block");
}
JSX:
<input style={{display: add}} onClick = {searchActive}/>
The code examples you gave are a bit short, so not sure what's happening there, but here is an example how to use the useState to manipulate style for example. Not the prettiest example, but one, as based on those pieces of code I think you might be using either the hooks or JSX in some incorrect way.
You should have a function that returns JSX, and in that function you should declare the state, or you could provide the state via props or context if needed, if the state is needed higher up in the hierarchy.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [display, setDisplay] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => setDisplay((prevDisplay) => !prevDisplay)}>
Button
</button>
<p style={{ display: display ? "block" : "none" }}>something here</p>
</div>
);
}
Codesandbox

useRef versus a file-scoped variable

I've been reading on why useRef is useful (e.g. in this SO answer and in the articles it links to) and it does make sense to me. However I notice that in my code I've "simply" solved the issue of how to store state in a functional component in a way that does not trigger re-renders by keeping the state as a global-scoped variable declared in the same file as the functional component.
I realize this isn't appropriate if the same component is rendered at the same time in multiple places on the DOM, as useRef supplies different state to different simultaneously rendered components whereas a file-scoped variable would be shared.
Is my mental model and assumptions correct and are there any other use cases or distinct advantages of useRef versus a file-scoped variable?
Is my mental model and assumptions correct ...
Not in the general case, even with your caveat. There are a couple of issues:
As you say, components can be instantiated more than once at the same thing (think: items in a list), but with your file-scoped (I assume you mean module-scoped) variable, all instances would use the same variable, causing cross-talk between the instances. With useRef, they'll each have their own non-state instance data.
Here's an example of the difference:
const { useState, useRef } = React;
let moduleScopedVariable = 0;
const TheComponent = () => {
const ref = useRef(0);
// Synthetic use case: counting renders
++ref.current;
++moduleScopedVariable;
return (
<div className="the-component">
<div>Render count (ref): {ref.current}</div>
<div>Render count (var): {moduleScopedVariable}</div>
</div>
);
};
const ids = [0, 1, 2];
const Example = () => {
const [counter, setCounter] = useState(0);
return (
<div>
Counter: {counter}
<input type="button" value="Increment" onClick={() => setCounter(c => c + 1)} />
<div>{ids.map((id) => <TheComponent key={id} />)}</div>
</div>
)
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
.the-component {
border: 1px solid black;
margin: 4px;
}
<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>
It's not just at the same time, there's crosstalk with a single instance that's mounted and unmounted over time:
Here's an example:
const { useState, useRef } = React;
let moduleScopedVariable = 0;
const TheComponent = () => {
const ref = useRef(0);
// Synthetic use case: counting renders
++ref.current;
++moduleScopedVariable;
return (
<div className="the-component">
<div>Render count (ref): {ref.current}</div>
<div>Render count (var): {moduleScopedVariable}</div>
</div>
);
};
const Example = () => {
const [flag, setFlag] = useState(true);
return (
<div>
Flag: {String(flag)}
<input type="button" value="Toggle" onClick={() => setFlag(b => !b)} />
{flag && <TheComponent />}
</div>
)
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
.the-component {
border: 1px solid black;
margin: 4px;
}
<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>
It also means the data remains lying around when the component is unmounted, which depending on what the data is could be an issue.
Fundamentally, it's great to reuse static data by closing over a module-scoped constant, but anything that changes within the component should be stored in state (in any of various guises) if it affects how the component renders, or a ref (usually) if not.

My button gives this error. Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()? [duplicate]

I have the following (using Material UI)....
import React from "react";
import { NavLink } from "react-router-dom";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
function LinkTab(link){
return <Tab component={NavLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
In the new versions this causes the following warning...
Warning: Function components cannot be given refs. Attempts to access
this ref will fail. Did you mean to use React.forwardRef()?
Check the render method of ForwardRef.
in NavLink (created by ForwardRef)
I tried changing to...
function LinkTab(link){
// See https://material-ui.com/guides/composition/#caveat-with-refs
const MyLink = React.forwardRef((props, ref) => <NavLink {...props} ref={ref} />);
return <Tab component={MyLink}
to={link.link}
label={link.label}
value={link.link}
key={link.link}
/>;
}
But I still get the warning. How do I resolve this issue?
Just give it as innerRef,
// Client.js
<Input innerRef={inputRef} />
Use it as ref.
// Input.js
const Input = ({ innerRef }) => {
return (
<div>
<input ref={innerRef} />
</div>
)
}
NavLink from react-router is a function component that is a specialized version of Link which exposes a innerRef prop for that purpose.
// required for react-router-dom < 6.0.0
// see https://github.com/ReactTraining/react-router/issues/6056#issuecomment-435524678
const MyLink = React.forwardRef((props, ref) => <NavLink innerRef={ref} {...props} />);
You could've also searched our docs for react-router which leads you to https://mui.com/getting-started/faq/#how-do-i-use-react-router which links to https://mui.com/components/buttons/#third-party-routing-library. The last link provides a working example and also explains how this will likely change in react-router v6
You can use refs instead of ref. This only works as it avoids the special prop name ref.
<InputText
label="Phone Number"
name="phoneNumber"
refs={register({ required: true })}
error={errors.phoneNumber ? true : false}
icon={MailIcon}
/>
In our case, we were was passing an SVG component (Site's Logo) directly to NextJS's Link Component which was a bit customized and we were getting such error.
Header component where SVG was used and was "causing" the issue.
import Logo from '_public/logos/logo.svg'
import Link from '_components/link/Link'
const Header = () => (
<div className={s.headerLogo}>
<Link href={'/'}>
<Logo />
</Link>
</div>
)
Error Message on Console
Function components cannot be given refs. Attempts to access this ref will fail.
Did you mean to use React.forwardRef()?
Customized Link Component
import NextLink from 'next/link'
import { forwardRef } from 'react'
const Link = ({ href, shallow, replace, children, passHref, className }, ref) => {
return href ? (
<NextLink
href={href}
passHref={passHref}
scroll={false}
shallow={shallow}
replace={replace}
prefetch={false}
className={className}
>
{children}
</NextLink>
) : (
<div className={className}>{children}</div>
)
}
export default forwardRef(Link)
Now we made sure we were using forwardRef in the our customized Link Component but we still got that error.
In order to solve it, I changed the wrapper positioning of SVG element to this and :poof:
const Header = () => (
<Link href={'/'}>
<div className={s.headerLogo}>
<Logo />
</div>
</Link>
)
If you find that you cannot add a custom ref prop or forwardRef to a component, I have a trick to still get a ref object for your functional component.
Suppose you want to add ref to a custom functional component like:
const ref = useRef();
//throws error as Button is a functional component without ref prop
return <Button ref={ref}>Hi</Button>;
You can wrap it in a generic html element and set ref on that.
const ref = useRef();
// This ref works. To get button html element inside div, you can do
const buttonRef = ref.current && ref.current.children[0];
return (
<div ref={ref}>
<Button>Hi</Button>
</div>
);
Of course manage state accordingly and where you want to use the buttonRef object.
to fix this warning you should wrap your custom component with the forwardRef function as mentioned in this blog very nicely
const AppTextField =(props) {return(/*your component*/)}
change the above code to
const AppTextField = forwardRef((props,ref) {return(/*your component*/)}
const renderItem = ({ item, index }) => {
return (
<>
<Item
key={item.Id}
item={item}
index={index}
/>
</>
);
};
Use Fragment to solve React.forwardRef()? warning
If you're using functional components, then React.forwardRef is a really nice feature to know how to use for scenarios like this. If whoever ends up reading this is the more hands on type, I threw together a codesandbox for you to play around with. Sometimes it doesn't load the Styled-Components initially, so you may need to refresh the inline browser when the sandbox loads.
https://codesandbox.io/s/react-forwardref-example-15ql9t?file=/src/App.tsx
// MyAwesomeInput.tsx
import React from "react";
import { TextInput, TextInputProps } from "react-native";
import styled from "styled-components/native";
const Wrapper = styled.View`
width: 100%;
padding-bottom: 10px;
`;
const InputStyled = styled.TextInput`
width: 100%;
height: 50px;
border: 1px solid grey;
text-indent: 5px;
`;
// Created an interface to extend the TextInputProps, allowing access to all of its properties
// from the object that is created from Styled-Components.
//
// I also define the type that the forwarded ref will be.
interface AwesomeInputProps extends TextInputProps {
someProp?: boolean;
ref?: React.Ref<TextInput>;
}
// Created the functional component with the prop type created above.
//
// Notice the end of the line, where you wrap everything in the React.forwardRef().
// This makes it take one more parameter, called ref. I showed what it looks like
// if you are a fan of destructuring.
const MyAwesomeInput: React.FC<AwesomeInputProps> = React.forwardRef( // <-- This wraps the entire component, starting here.
({ someProp, ...props }, ref) => {
return (
<Wrapper>
<InputStyled {...props} ref={ref} />
</Wrapper>
);
}); // <-- And ending down here.
export default MyAwesomeInput;
Then on the calling screen, you'll create your ref variable and pass it into the ref field on the component.
// App.tsx
import React from "react";
import { StyleSheet, Text, TextInput, View } from "react-native";
import MyAwesomeInput from "./Components/MyAwesomeInput";
const App: React.FC = () => {
// Set some state fields for the inputs.
const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState("");
// Created the ref variable that we'll use down below.
const field2Ref = React.useRef<TextInput>(null);
return (
<View style={styles.app}>
<Text>React.forwardRef Example</Text>
<View>
<MyAwesomeInput
value={field1}
onChangeText={setField1}
placeholder="field 1"
// When you're done typing in this field, and you hit enter or click next on a phone,
// this makes it focus the Ref field.
onSubmitEditing={() => {
field2Ref.current.focus();
}}
/>
<MyAwesomeInput
// Pass the ref variable that's created above to the MyAwesomeInput field of choice.
// Everything should work if you have it setup right.
ref={field2Ref}
value={field2}
onChangeText={setField2}
placeholder="field 2"
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1,
justifyContent: "center",
alignItems: "center"
}
});
export default App;
It's that simple! No matter where you place the MyAwesomeInput component, you'll be able to use a ref.
I just paste here skychavda solution, as it provide a ref to a child : so you can call child method or child ref from parent directly, without any warn.
source: https://github.com/reactjs/reactjs.org/issues/2120
/* Child.jsx */
import React from 'react'
class Child extends React.Component {
componentDidMount() {
const { childRef } = this.props;
childRef(this);
}
componentWillUnmount() {
const { childRef } = this.props;
childRef(undefined);
}
alertMessage() {
window.alert('called from parent component');
}
render() {
return <h1>Hello World!</h1>
}
}
export default Child;
/* Parent.jsx */
import React from 'react';
import Child from './Child';
class Parent extends React.Component {
onClick = () => {
this.child.alertMessage(); // do stuff
}
render() {
return (
<div>
<Child childRef={ref => (this.child = ref)} />
<button onClick={this.onClick}>Child.alertMessage()</button>
</div>
);
}
}

React how to add class to parent element when child element is clicked

This is my code.
export default MainContent = () => {
handleClick = (e) => {
// This is where I got confused
};
return (
<>
<div>
<div onClick={handleClick}>1</div>
</div>
<div>
<div onClick={handleClick}>2</div>
</div>
<div>
<div onClick={handleClick}>3</div>
</div>
</>
);
}
What I want is to add a class to parent div when child element is clicked. I couldn't use useState() since I only need one element to update. Couldn't use setAttribute since it changes the same element. Is there any solution for that?
I take it you want to apply the class only to direct parent of clicked child.
create a state to oversee different clicked child div
apply the class only to direct parent of clicked* child div based on the state
make use of clsx npm package (since we don't wanna overwrite parent div styling)
you may see the working examples here: https://codesandbox.io/s/happy-babbage-3eczt
import { useState } from "react";
import "./styles.css";
import styled from "styled-components";
import classnames from "clsx";
export default function App() {
const [styling, setstyling] = useState({
status: false,
from: "",
style: ""
});
function handleClick(childNo) {
setstyling({ status: true, from: childNo, style: "applyBgColor" });
}
return (
<div className="App">
<Styling>
<div
className={
styling?.status && styling?.from == "child-1"
? classnames("indentText", styling?.style)
: "indentText"
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-1`)}
>1</div>
</div>
<div
className={
styling?.status && styling?.from == "child-2"
? styling?.style
: ""
}
>
<div
className="whenHoverPointer"
onClick={() => handleClick(`child-2`)}
>2</div>
</div>
</Styling>
</div>
);
}
const Styling = styled.div`
.indentText {
font-style: italic;
}
.applyBgColor {
background-color: grey;
}
.whenHoverPointer {
cursor: pointer;
}
`;
function Item({ children }) {
const [checked, isChecked] = useState(false);
const onClick = () => isChecked(true);
return (
<div {...(isChecked && { className: 'foo' })}>
<button type="button" onClick={onClick}>{children}</button>
</div>
);
}
function MainContent() {
return [1, 2, 3].map(n => <Item key={n}>{n}</Item>);
}
I think theirs something wrong, useState and JSX will update related part, react will handling that itself, but base on logic, may you need to prevent re-render to stop issue, for example, here on handleClick, will keep re-render in each state since function will re-addressed in memory each update..
any way, base on your question, you can do that by this:
const handleClick = useCallback((e) => {
e.target.parentElement.classList.add('yourClass')
}, []);
But I believe its a Bad solution.
What I recommend is solve issue by state to keep your react life cycle is fully work and listen to any update, also you can use ref to your wrapper div and add class by ref.

Why does React re-render children when the parent changes?

If a parent re-renders, children in React are also re-rendered, no matter if the passed props changed or not.
Why is React doing that? What would be the issue if React wouldn't re-render children (without changed props) when the parent renders?
Update: I am talking about this in the React Devtools profiler:
Sample code:
App.tsx:
import React, { useMemo, useState } from "react";
import "./App.css";
import { Item, MyList } from "./MyList";
function App() {
console.log("render App (=render parent)");
const [val, setVal] = useState(true);
const initialList = useMemo(() => [{ id: 1, text: "hello world" }], []); // leads to "The parent component rendered"
//const initialList: Item[] = []; // leads to "Props changed: (initialList)"
return (
<div style={{ border: "10px solid red" }}>
<button type="button" onClick={() => setVal(!val)}>
re-render parent
</button>
<MyList initialList={initialList} />
</div>
);
}
export default App;
MyList.tsx:
import { FC, useState } from "react";
export interface Item {
id: number;
text: string;
}
interface Props {
initialList: Item[];
//onItemsChanged: (newItems: Item[]) => void;
}
export const MyList: FC<Props> = ({
initialList,
//onItemsChanged,
}) => {
console.log("render MyList");
const [items, setItems] = useState(initialList);
return (
<div style={{ border: "5px solid blue" }}>
<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
<button type="button">add list item (to be implemented)</button>
</div>
);
};
React achieves a fast and responsive UI by re-rendering components on every state change (using setState) or from changes of props, followed by React’s reconciliation diffing algorithm that diffs previous renders with current render output to determine if React should commit changes to the component tree (e.g. DOM) with the new updates.
However, unnecessary component re-renders will happen and can be expensive, It’s been a common performance pitfall in every single React project that I’ve been working on. SOURCE
Solution for this issue :
A component can re-render even if its props don’t change. More often than not this is due to a parent component re-rendering causing the child to re-render.
To avoid this, we can wrap the child component in React.memo() to ensure it only re-renders if props have changed:
function SubComponent({ text }) {
return (
<div>
SubComponent: { text }
</div>
);
}
const MemoizedSubComponent = React.memo(SubComponent);
SOURCE
Memoization generates an additional cost corresponding to cache-related computations, this is why React re-renders components even when the props are referentially the same, unless you choose to memoize things using React.memo for instance.
If you consider for example a component that re-renders with different props very often, and if memoization was an internal implementation detail, then React would have to do 2 jobs on every re-rendering:
Check if the old and current props are referentially the same.
Because props comparison almost always returns false, React would then perform the diff of previous and current render results.
which means that you might end up with worse performance.
Wrapp your component with React.memo and it will not re-render
export const MyList: FC<Props> = React.memo(({
initialList,
//onItemsChanged,
}) => {
console.log("render MyList");
const [items, setItems] = useState(initialList);
return (
<div style={{ border: "5px solid blue" }}>
<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
<button type="button">add list item (to be implemented)</button>
</div>
);
})
If you are looking at reason, please see this
Well, the component only re-renders if shouldComponentUpdate() returns true, which it does by default. It is usually not much of a problem because the DOM will still not update if there are no changes. As an optimization, you can still add logic to keep your child components from re-rendering if its state and props have not changed, as follows:
shouldComponentUpdate(nextProps, nextState) {
return (this.props.someProp !== nextProps.someProp && this.state.someState !== nextState.someState);
}
Do remember though this is just a performance optimization, when in doubt stick to the default behaviour. Here is a link to the official documentation.
Here is a little analogy that should help
Let's say you have a box, and a box within that box. If you want to replace the outer box with a new box, then you must also replace the box inside it.
Your parent component is like the outer box, and the child component like the inner box. If you clear away the first render to make room for a new render, that new render will re-render new child components inside it.
I hope this helps clear things up for you.
Edit:
If you were not to re-render the child component inside the parent then any props that might be passed to the child component would not update within it and therefore, the child would be forever without dynamic props, and the whole purpose of ReactJS would be lost. You wouldn't be here to ask this question in the first place anyway if that was the case.
When Parent Component render will not render child components,for using memo and useCallback.
Child Component:
Using memo will cause React to skip rendering a component if its props have not changed.
import { React, memo } from "react";
import { Typography, TextField } from "#mui/material";
function PasswordComponent(props) {
console.log("PasswordComponenmt");
return (
<div>
<Typography style={{ fontWeight: "900", fontSize: 16 }}>
Password
</Typography>
<TextField
size="small"
type="password"
variant="filled"
value={props.password}
onChange={(e) => {
props.onChangePassword(e.target.value);
}}
/>
</div>
);
}
export default memo(PasswordComponent);
Parent Component:
The React useCallback Hook returns a memoized callback function.
The useCallback Hook only runs when one of its dependencies update.
This can improve performance.
import { React, useState, useCallback } from "react";
export default function ParentComponent() {
const [password, setpassword] = useState("");
const onChangePassword = useCallback((value) => {
setpassword(value);
}, []);
return <PasswordComponent onChangePassword={onChangePassword} />;
}

Resources