Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does React re-use child components / keep the state of child components when re-rendering the parent component?

In React, every time a component is rendered/re-rendered, it regenerates all of it's child nodes/components using createElement. How does React know when to persist the components state between re-renders?

As an example, consider the following code:

class Timer extends Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
  }
  tick() {
    this.setState(state => ({ seconds: state.seconds + 1 }));
  }
  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }
  componentWillUnmount() {
    clearInterval(this.interval);
  }
  render() {
    return createElement('div', null,
      'Seconds: ',
      this.state.seconds
    );
  }
}
class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { clicks: 0 };
  }
  click() {
    this.setState(state => ({ clicks: state.clicks + 1 }));
  }
  render() {
    return createElement('button', { onClick: () => this.click() },
      createElement(Timer, null),
      'Clicks: ',
      this.state.clicks
    );
  }
}
render(createElement(Button, null), document.getElementById('root'));

You can try this code with the Preact REPL here.

Notice that when the button is pressed and the clicks value is updated, the state of the Timer component persists and is not replaced. How does React know to re-use the component instance?

While this may seem like a simple question at first, it becomes more complex when you consider stuff like changing the props passed to a child component or lists of child components. How does React handle changing the props of a child component? Does the child component's state persist even though it's props have changed? (In Vue, the state of a component does persist when it's props change) How about lists? What happens when an entry in the middle of a list of child components is removed? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.

like image 200
luawtf Avatar asked Dec 27 '20 07:12

luawtf


People also ask

Does child component Rerender if parent state changes?

State changes in Child component doesn't effect on the parent component, but when a state of parent component changes all the child components render.

Do kids Rerender when parent Rerenders React?

“children” is a <ChildComponent /> element that is created in SomeOutsideComponent . When MovingComponent re-renders because of its state change, its props stay the same. Therefore any Element (i.e. definition object) that comes from props won't be re-created, and therefore re-renders of those components won't happen.

Will a React component reset its state when it re renders?

No, The state will remain as it is until your component unmounts. If you want to trigger something while unmounting then you can use useEffect hook.

How to change parent component state from child component in react?

React hooks are introduced in React 16.8. If you are familiar with the class components then there is no difference to change the parent component state from child component. In both cases, you have to pass the callback function to the parent. Let’s take a very simple example to understand it. We will take two components, Parent and Child.

How to re-render a child component?

One easy option to re-render a child is to update a unique key attribute every time you need a re-render. Show activity on this post. your approach is wrong dont update you parent state from child like this and don't save props in child state they are already updated as the parent updates.

What happens when a parent component passes her state to child components?

However, I am asking this in case I am reinventing the wheel since I am very new to React. I was under the impression that if a parent component passes her state to the child components via props than upon updating the parent's state, the child will re-render if needed.

Why ref property does not exist in intrinsicattributes&{ children?: reactnode?

Property 'ref' does not exist on type 'IntrinsicAttributes & { children?: ReactNode; }' All this really means, is that functional components don’t have an instance. They’re stateless. But if your child component is a class type, then this is how you get the state from it with React useRef hook or createRef ().


Video Answer


2 Answers

createElement vs render vs mount

When a React Component such as your Button is rendered, a number of children are created with createElement. createElement(Timer, props, children) does not create an instance of the Timer component, or even render it, it only creates a "React element" which represents the fact that the component should be rendered.

When your Button is rendered, react will reconcile the result with the previous result, to decide what needs to be done with each child element:

  • If the element is not matched to one in the previous result, then a component instance is created then mounted then rendered (recursively applying this same process). Note that when Button is rendered for the first time, all of the children will be new (because there is no previous result to match against).
  • If the element is matched to one in the previous result, then the component instance is reused: its props are updated, then the component is re-rendered (again, recursively applying this same process). If the props did not change, React might even choose not to re-render as an efficiency.
  • Any elements in the previous result that was not matched to an element in the new result will be unmounted and destroyed.

React's diffing algorithm

An element "matches" another one if React compares them and they have the same type.

The default way for React to compare children, is to simply iterate over both lists of children at the same time, comparing the first elements with each other, then the second, etc.

If the children have keys, then each child in the new list is compared to the child in the old list that has the same key.

See the React Reconciliation Docs for a more detailed explanation.

Examples

Your Button always returns exactly one element: a button. So, when your Button re-renders, the button matches, and its DOM element is re-used, then the children of the button are compared.

The first child is always a Timer, so the type matches and the component instance is reused. The Timer props did not change, so React might re-render it (calling render on the instance with the same state), or it might not re-render it, leaving that part of the tree untouched. Both of these cases would result in the same result in your case - because you have no side-effects in render - and React deliberately leaves the decision of when to re-render as an implementation detail.

The second child is always the string "Clicks: " so react leaves that DOM element alone too.

If this.state.click has changed since the last render, then the third child will be a different string, maybe changing from "0" to "1", so the text node will be replaced in the DOM.


If Buttons render were to return a root element of a different type like so:

  render() {
    return createElement(this.state.clicks % 2 ? 'button' : 'a', { onClick: () => this.click() },
      createElement(Timer, null),
      'Clicks: ',
      this.state.clicks
    );
  }

then in the first step, the a would be compared to the button and because they are different types, the old element and all of its children would be removed from the DOM, unmounted, and destroyed. Then the new element would be created with no previous render result, and so a new Timer instance would be created with fresh state, and the timer would be back at 0.


Timer matches? previous tree new tree
no match <div><Timer /></div> <span><Timer /></span>
match <div>a <Timer /> a</div> <div>b <Timer /> b</div>
no match <div><Timer /></div> <div>first <Timer /></div>
match <div>{false}<Timer /></div> <div>first <Timer /></div>
match <div><Timer key="t" /></div> <div>first <Timer key="t" /></div>
like image 129
BudgieInWA Avatar answered Nov 02 '22 23:11

BudgieInWA


Never used Vue, but this is my take.

Does the child component's state persist even though it's props have changed? (In Vue, the state of a component does persist when it's props change)

This depends on how you handle the props in your child.

This child will re-render every time you change (mutate) your props.

const Child = (props) => {
    return <div>{ props.username }</div>;
};

This child will not re-render when props change since the return value is dependent on the local state, and not the props.

const Child = (props) => {
    const [state, setState] = useState(props.username);
    return <div>{ state }</div>;
};

This child will re-render when props change, as local state updates with the new props.

const Child = (props) => {
    const [state, setState] = useState(props.username);

    useEffect(() => {
        // changing props changes the component's state, causing a re-render
        setState(props.username); 
    }, [props]);

    return <div>{ state }</div>;
};

As seen in the examples above, the programmer is the one in control of whether React triggers a re-render of a child.

How about lists? What happens when an entry in the middle of a list of child components is removed? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.

When a list of children is involved (eg. when .map is used) React will require the key parameter, so that React will be aware what was add/removed/changed between parent component re-renders. React requires that the same key be used for the same components to prevent unnecessary re-renders (don't use Math.random() as your key).

like image 31
Someone Special Avatar answered Nov 03 '22 00:11

Someone Special