ReactJS children component lost its state when rendered with a different function

Viewed 2575

When writing a ReactJS component, I found the child component loses its state once a while. Turns out it happens if the child component is rendered with a different render function.

i.e.

Parent component has 2 sub-render functions and, due to different conditions, it either renders the child component in the body, or in the header.

    ...

    renderInHeader = () => (<Header><ChildComponent /></Header>);

    renderInBody = () => (<Body><ChildComponent /><Body>);

    render = () => {
      if (somethingHappens) {
        return this.renderInHeader();
      }
      return this.renderInBody();
    }

    ...

That means, when somethingHappens, it will use a different render function to render the ChildComponent.

Then, ChildComponent would lose its state. (Child component's state get reset).


I understand why it happens, I'm actually slightly surprised that the child's state doesn't get reset more often (i.e. reset every time parent re-render that node).

However, what's the ideal solution to deal with it?

  • Avoid using state in child component? (because it's not guaranteed safe?)

  • Give a key= to the ChildComponent, so it can be treated as an independent node during re-render? (not sure if key works outside of mapper)

  • Use ref? (no idea if it works that way)

  • Use new "Portal" from react 16? (haven't tried yet)


Note: I know I can use variable for Header and Body, so there's only one render function needed. However, a real world case could be more complicated, there may be multiple child components distributed in different areas.

i.e.

        ...

    renderInBody = () => (
        <Body>
            <Header>
                <ChildComponent1 />
                <ChildComponent2 />
            </Header>
            <ChildComponent3 />
        </Body>
    );

    renderInHeader = () => (
        <Header>
            <ChildComponent3 />
            <ChildComponent1 />
            <Body>
                <ChildComponent2 />
            </Body>
        </Header>
    );

    render = () => {
      if (somethingHappens) {
        return this.renderInHeader();
      }
      return this.renderInBody();
    }

    ...

Here, Child Component 1,2,3 doesn't really change, just their position and parent's structure gets changed.

And, I don't feel it's a good solution to put the Child Component positioning strategy in Header and Body components.

Is it possible to keep ChildComponent's state?

3 Answers

You can define variables for your child components and use them to order the rendering in the parent component. So your example code would become

renderInBody = (child1,child2,child3) => (
    <Body>
        <Header>
            {child1}
            {child2}
        </Header>
        {child3}
    </Body>
);

renderInHeader = (child1,child2,child3) => (
    <Header>
        {child3}
        {child1}
        <Body>
            {child2}
        </Body>
    </Header>
);

render = () => {
    let child1=<ChildComponent key={1}/>;
    let child2=<ChildComponent key={2}/>;
    let child3=<ChildComponent key={3}/>;
      if (somethingHappens) {
        return this.renderInHeader(child1,child2,child3);
      }
      return this.renderInBody(child1,child2,child2);
}

Make sure that your child components have unique keys so that they can maintain state.

ChildComponent should not be a part of if-else block since it is always rendered .

render = () => {
  const ParentComponent = somethingHappens ? Header : Body;
  return (
    <ParentComponent>
      <ChildComponent />
    </ParentComponent>
  );
};

Its better,if you create new instance of the ChildComponent every-time.

let alwaysNewChildComp  = <ChildComponent />

renderInHeader = () => (<Header>{alwaysNewChildComp}</Header>);

renderInBody = () => (<Body>{alwaysNewChildComp}<Body>);

OR

let alwaysNewChildComp = React.createClass({

      render: function () {
        return (<div><ChildComponent /></div>)
      }
})

renderInHeader = () => (<Header>{alwaysNewChildComp}</Header>);

renderInBody = () => (<Body>{alwaysNewChildComp}<Body>);

It will reset your ChildComponent state.

You can go further with redux also.

Related