Child re-rendering because of function? - reactjs

I'm having trouble navigating these concepts where a child keeps re-rendering because I'm passing it a function from the parent. This parent function references an editor's value, draftjs.
function Parent() {
const [doSomethingValue, setDoSomethingValue] = React.useState("");
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
const editorRef = useRef<HTMLInputElement>(null);
const doSomething = () => {
// get draftjs editor current value and make a fetch call
let userResponse = editorState.getCurrentContent().getPlainText("\u0001");
// do something with userResponse
setDoSomethingValue(someValue);
}
return (
<React.Fragment>
<Child doSomething={doSomething} />
<Editor
ref={editorRef}
editorState={editorState}
onChange={setEditorState}
placeholder="Start writing..." />
<AnotherChild doSomethingValue={doSomethingValue}
<React.Fragment>
}
}
My Child component is simply a button that calls the parent's doSomething and thats it.
doSomething does its thing and then makes a change to the state which is then passed to AnotherChild.
My problem is that anytime the editorState is updated (which is every time you type within the editor), my Child component re-renders. Isn't that unnecessary? And if so, how could I avoid this?
If I was passing my Child component a string and leveraged React.Memo, it does not re-render unless the string changes.
So what am I missing with passing a function to the child? Should my child be re-rendering everytime?

React works on reference change detection to re-render components.
Child.js: Wrap it under React.memo so it becomes Pure Component.
const Child = ({doSomething}) => <button onClick={doSomething}>Child Button Name</button>;
export default React.memo(Child);
Parent.js -> doSomething: On every (re)render, callbacks are also recreated. Make use of useCallback so that your function is not recreated on every render.
const doSomething = React.useCallback(() => {
let userResponse = editorState.getCurrentContent().getPlainText("\u0001");
setDoSomethingValue(someValue);
}, [editorState]);
Side Note
On broader lines, memo is HOC and makes a component Pure Component. useMemo is something which cache the output of the function. Whereas useCallback caches the instance of the function.
Hope it helps.

If you do not want your component to be re-rendered every time your parent is re-rendered, you should take a look at useMemo.
This function will only recalculate its value (in your case, your component) whenever its second argument changes (here, the only thing it depends on, doSomething()).
function Parent() {
const [editorState, setEditorState] = React.useState(
EditorState.createEmpty()
);
const editorRef = useRef<HTMLInputElement>(null);
const doSomething = () => {
// get draftjs editor current value and make a fetch call
let userResponse = editorState.getCurrentContent().getPlainText("\u0001");
// do something with userResponse
}
const childComp = useMemo(() => <Child doSomething={doSomething} />, [doSomething])
return (
<React.Fragment>
{childComp}
<Editor
ref={editorRef}
editorState={editorState}
onChange={setEditorState}
placeholder="Start writing..." />
<React.Fragment>
}
}
If doSomething does not change, your component does not re-render.
You may also want to use useCallback for your function if it is doing heavy calculations, to avoid having it re-compiled every time your component renders: https://reactjs.org/docs/hooks-reference.html#usecallback

Take a look into PureComponent, useMemo, or shouldComponentUpdate
I would also add, instead of passing down the function to do the rendering of a top level component, pass the value and define the function later down in the component tree.

if you want to avoid unnecessary re-renders, you can use React.memo and the hook useCallback. Take a look at the following sandbox.
The button1 is always re-rendered because it take a callback that is not memoized with useCallback and the button2 is just rendered the first time even if the state of the parent has changed (take a look at the console to check the re-renders). You have to use React.memo in the Child component that is the responsible to render the buttons.
I hope it helps.

Related

How to pass data from child component to parent component without the child component rerendering in React?

My child component is an accordion with controls in it. The problem is, everytime you input data on the controls and it triggers an onChange event which passes its data to its parent component, the accordion closes/collapses and I don't need that behavior.
The accordion is just imported from a library and I can't change its behavior.
Is there anyway to prevent this or pass props/data from child component to parent component without rerendering/restarting DOM of child component?
Parent component:
const ParentComponent = () => {
const handleChangeChildAccordion = (e) => {
console.log(e);
}
return (
<ChildComponentAccordion currentData={currentData} onChangeTPAccordion={handleChangeChildAccordion}/> }
);
}
Child Component:
const ChildComponent = (props) => {
const onDataChangeHandler = (e) => {
props.onChangeTPAccordion(e);
};
return (
<Accordion onChange={onDataChangeHandler}/>
);
}
The child component doesn't remount, it rerenders. Normally, a component rerenders any time any its parent rerenders. To avoid that you:
Avoid changing the child component's props, and
Memoize the child component so it doesn't rerender when its props don't change in a way it cares about.
You do the first by ensuring that, for instance, callbacks you pass the component are stable (they don't change on every render). In a function component, you do it by using useMemo, useCallback, and/or useRef. In a class component, you typically do that by making the callbacks properties on the component instance that aren't recreated.
You do the second by using React.memo (if it's a function component) or for a class component by extending PureComponent (for simple memoization) or implementing shouldComponentUpdate.
You haven't given us much code to work with, so here's a simple example:
const { useState, useCallback, useRef } = React;
const log = (...msgs) => console.log(...msgs);
const Child1 = React.memo(({value, doUpdate}) => {
log(`Child1 rendering`);
return <div>
Child1 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Child2 = React.memo(({value, doUpdate}) => {
log(`Child2 rendering`);
return <div>
Child2 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Parent = () => {
log(`Parent rendering`);
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
// `useCallback` remembers the first function you pass it and returns
// it to you, only returning the new one you pass it if the
// dependencies array changes. This is one way to create a stable
// callback.
const update1 = useCallback(() => setCounter1(c => c + 1), []);
// `useRef` gives you an object you can put arbitrary properties on,
// which is another way to have a stable callback.
const update2Ref = useRef(null);
if (!update2Ref.current) {
update2Ref.current = () => setCounter2(c => c + 1);
}
const update2 = update2Ref.current;
return <div>
<Child1 value={counter1} doUpdate={update1} />
<Child2 value={counter2} doUpdate={update2} />
</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
TypeScript playground with types added
That shows two different ways of making acallback stable (useCallback and useRef). (useMemo is what useCallback uses under the hood, so I didn't do an example of it.)
In that example, notice how the child telling the parent to update its counter doesn't cause the other child to re-render. That's because:
We don't change the other child's props (because our callbacks are stable and the other child's counter didn't change), and
We've memoized the child components
You can customize whether the component re-renders by passing a second argument to React.memo which is a function that says whether the new props coming in are "the same" (for rendering purposes) as the previous ones. By default, React.memo does a simple === comparison on each prop. And again, for class components, PureComponent does the simple === comparison, or you can implement shouldComponentUpdate yourself for more finely-tuned checks.

Life cycle of props with state in react

I couldn't find any clear information or video about when the props are received in the react component life-cycle. Let's make an example:
export const App = () => {
const [count, setCount] = useState(0);
console.log(A, count)
return (
<Component count={count} justText={'hello'} onClick={() => setState(state => state + 1)}/>
);
}
const Component = ({count, justText}) => {
const [count2, setCount2]=useState(0)
console.log("B ",count)
console.log("C ",justText)
console.log("D",count2)
useEffect(()=> {
console.log("F", count)
console.log("G",count2)
});
return (
<>
{console.log(E,count)}
</>
)
}
I would like to know the execution order and why is that, because sometimes I found that when passing props with state down, the component which received them gets undefined but in the component where it is declared it is already initialized. Sometimes I need to wait the useEffect to get the value, so I am lost in what happens with the props during the lifecycle when mounting, and updating. I tried to find information about it but couldn't see anywhere where they explain the props lifecycle.
Thank you!
PD: Just setted random values for A,B,C...they don't follow any order.
You won't get undefined while console logging the props in the function or console logging the state :
import React from "react";
function Component() {
const [state, setState] = React.useState(false);
console.log(state);
return <Other state={state}></Other>;
}
export default Component;
import React from "react";
function Other({ state }) {
console.log(state);
React.useEffect(() => {
console.log(state);
}, []);
return <div>Other component</div>;
}
export default Other;
React components gets states and props before mounting so they will never be undefined when you want to use them .
TLDR
Lifecycle of a props starts when it gets mount and stays until it gets called or re-rendered by the parent that is using it. When it re-rendered by the parent it gets the props again but it can be unchanged or changed.
Full story
The lifecycle of a props is very straight forward. When your component is being called from a Parent it's props is also passed at that time. Think props as the function parameter. Suppose you have a function like below
const fn = (param1, param2) => {
// your function implementation
}
Here when you call the function you call it with the params like this
fn(val1, val2)
So each time you call the function you pass the params. If any time calling the function any of the param is not passed it will get null
A component and it's props is also same. Here the Component is the function and the props are the params. Every time the components gets called or rendered it is called or rendered with the props.
Now, If your parent that is using the component, pass all the props then the component will get the value. But if it doesn't pass all the props it will get null.
Like function you can also define the initial value of a props like this
const Component1 = ({props1='val1', props2='val2', ...rest}) => {
return()
}
In the code above even if you do not pass props1 and props2 from the parent. Still it will not be null, it will have the initial value.

React useCallback with onClick not working. Rerenders child component

TimeChild re-renders in below image even after using useCallback
When Time sets state, then Time is going to rerender. Then, unless you do something to stop it, all of its children will rerender too. If you want to stop a child from rerendering, then the child component needs to use React.memo.
const TimeChild = React.memo(() => {
// ...
});
If you do this, then when TimeChild would render, it will first do an === comparison between each of its old props and each of its new props. If they are all the same, TimeChild will skip rendering.
The only role that useCallback plays in this is if TimeChild receives a function as a prop. If it does, then you need to make sure it receives the same function each time, or React.Memo will never be able to skip rendering because its props keep changing. But in your example there are no props at all being passed to TimeChild, so useCallback is not necessary.
You can use 'useCallback' in this way :
import React, { useCallback, useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
const callBckValue = useCallback(() => {
setCount((count) => count + 1);
}, []);
return (
<div>
<h2>{count}</h2>
<button type="button" onClick={callBckValue}>
Click Me
</button>
</div>
);
};
export default App;
Firstly, you need to be aware, only in special situation, it makes sense to stop child component from re-rendering. If your case is not that special, that might not be a good idea.
Secondly, if you are sure you have to do it, use React.memo, the usage is pretty like componentShouldUpdate in class component

React: How to pass state to child component and call function in child to use this state

I am implementing a component in functional components where there are several other child components in it passing data to each other. I need to pass data from parent to child component and call some function there to use it.
In class componenets we use componentdidupdate but could not understand how to do in functional component.
One idea is to use useEffect hook but could not do with it.
Im going to take a stab here, because we dont have context or code to go with.
useEffect accepts a dependency array to which it will react when a value or object reference changes
const ChildComponent = (props) => {
const {
valuePassedFromParent
} = props;
const actionFunction = (value) => {
//perform some tasks with value passed from parent when it changes
}
//this is similar to componentDidUpdate, but it reacts to changes in valuePassedFromParent though props since its in the useEffect dependency array
useEffect(() => {
//do something with valuePassedFromParent
actionFunction(valuePassedFromParent);
},[valuePassedFromParent]);
return (
<div>
</div>
)
}
You cas use useEffect to reproduce the behavior of componentdidupdate like this :
const [myState,setMyState] = useState();
useEffect(() => {
...
},[myState]);
The function use effect will run every time myState will be updated, like componentdiduptate would have do.
In your case, the state is given by the parent component if I understand well, so just replace the myState in the array dependency by the state given through the prop of your child component.

Does React renders Component due to props?

According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.

Resources