I made a small experiment while learning React
https://codepen.io/bullet03/pen/abGoGvL?editors=0010:
React 17.0 with Fiber
2 components: Agregator(Parent) and Counter(Child)
Counter(Child) uses useState hook to change it's state
NB!!! Agregator(Parent) doesn't return JSX component, but calls Counter(Child) function. I know it's not casual way of using React, but this is the essence of the experiment
when click the button of Counter(Child) component we expect only Counter(Child) to be rerendered, but somehow Agregator(Parent) rerenders as well according to the console.log
Here comes the question: Why change of Counter(Child) component state causes Agregator(Parent) component rerender?
function Agregator() {
console.log("render Agregator");
return Counter();
}
function Counter() {
const [count, setCount] = React.useState(0);
const incCount = () => setCount((prev) => prev + 1);
console.log("render Counter", count);
return <button onClick={incCount}>{count}</button>;
}
ReactDOM.render(<Agregator />, document.getElementById("root"));
As far as I know, it shouldn't happen, because rerender of the component is caused by several cases:
change of state
change of props
change of context
forceUpdate
rerender of ancestor component
None of this is applicable to our case
NB!!! No need to rewrite this experiment to correct one, but it would be greate to get the explanation why in my case it works like this, so change of Child component state causes Parent component to rerender.
NB!!! Agregator(Parent) doesn't return JSX component, but calls Counter(Child) function. I know it's not casual way of using React, but this is the essence of the experiment
Then there's only one component at all, and no parent/child relationship, which renders (ha!) your experiment invalid.
Consider what would happen if you used your IDE's Inline Function feature on the Counter() invocation (which is essentially what the JS interpreter does, but with call stack and all that):
function Agregator() {
console.log("render Agregator");
// inlined...
const [count, setCount] = React.useState(0);
const incCount = () => setCount((prev) => prev + 1);
console.log("render Counter", count);
return <button onClick={incCount}>{count}</button>;
// ... end of inline.
}
ReactDOM.render(<Agregator />, document.getElementById("root"));
Related
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.
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
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.
I know the gist of converting React Class components to functional components, but I found an instance where I nor my searching on the internet know the answer.
export default class Counter extends Component<Props, State> {
count = 0
updateCount = ()=> this.count +=1
render() {
return <div onClick={this.updateCount}>{this.count}</div>
}
}
Ignore the ugliness of the class, but how would i carry the count over into a functional component, with useRef?
The reason I ask is because in another class i am trying to convert, I have an async PromisePool running, that updates the downloaded variable each time a promise finishes, and when I tried to put downloaded into the state, it would always rerender the component and lose the data.
With useRef, you can create a variable which is not initialized on each re-render. The above component would look like
export default () => {
const count = useRef(0);
const updateCount = ()=> count.current +=1
render() {
return <div onClick={updateCount}>{count}</div>
}
}
However, you must know that updating a ref doesn't cause a re-render and hence updated value won't reflect in render
If you wish to trigger a re-render, make use of useState instead
export default () => {
const [count, setCount] = useState(0);
const updateCount = ()=> setCount(prevCount => prevCount + 1);
render() {
return <div onClick={updateCount}>{count}</div>
}
}
This is a direct example of moving the component over to a functional component. The count will remain in use as long as the component doesn't fully remount. It will remain even after re-renders.
export default function Counter(){
const [count,setCount] = useState(0);
return <div onClick={()=>setCount(count=>count+1)}>{count}</div>
}
Unless you are remounting this component by using a different key or changing the dom above it (i.e. adding a wrapping div after your promise finishes), then this should work fine (but in the instance of remounting, the class component would also reset it's counter).
You cannot carry the updated value of count with useRef since no rerender occurs when count is updated. useRef is for persisting an object in a component over multiple renders.
A possible solution for your PromisePools issue: Instead of converting the parent into a functional component, make use of the shouldComponentUpdate() lifecycle method in your class component to prevent a rerender of the child component when the state is changed.
Take a look at the lifecycle docs for more info:
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
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.