Home > Software design >  Why doesn't my React child component get value (re-render) when I change the parent state?
Why doesn't my React child component get value (re-render) when I change the parent state?

Time:11-15

Background

I wrote an exact, short yet complete example of a Parent component with a nested Child component which simply attempts:

  1. Alter a string in the Parent's state
  2. See the Child component updated when the Parent's state value is altered (this.state.name)

Here's What It Looks Like When the app loads a default value is passed from Parent state to child props.

Parent / Child component

Change The Name

All I want to do is allow the change of the name after the user adds a new name in the Parent's <input> and clicks the Parent's <button>

However, as you can see, when the user clicks the button only the Parent is rendered again.

Questions

  1. Is it possible to get the Child to render the new value?
  2. What am i doing wrong in this example -- why isn't it updating or rendering the new value?

Parent change is rendered

All Source Code

Here is all of the source code and you can view it and try it in my StackBlitz project.

I've kept it as simple as possible.

Parent component (DataLoader)

import * as React from 'react';
import { useState } from 'react';
import { Grid } from './Grid.tsx';

interface LoaderProps {
  name: string;
}

export class DataLoader extends React.Component<LoaderProps, {}> {
  state: any = {};
  constructor(props: LoaderProps) {
    super(props);

    this.state.name = this.props.name;

    this.changeName = this.changeName.bind(this);
  }

  render() {
    const { name } = this.state;
    let parentOutput = <span>{name}</span>;
    return (
      <div>
        <button onClick={this.changeName}>Change Name</button>
        <input id="mapvalue" type="text" placeholder="name" />
        <hr id="parent" />
        <div>### Parent ###</div>
        <strong>Name</strong>: {parentOutput}
        <hr id="child" />
        <Grid childName={name} />
      </div>
    );
  }

  changeName() {
    let newValue = document.querySelector('#mapvalue').value.toString();
    console.log(newValue);
    this.setState({
      name: newValue,
    });
  }
}

Child component (Grid)

import * as React from 'react';

interface PropsParams {
  childName: string;
}

export class Grid extends React.Component<PropsParams, {}> {
  state: any = {};

  constructor(props: PropsParams) {
    super(props);
    let counter = 0;

    this.state = { childName: this.props.childName };
    console.log(`CHILD -> this.state.name : ${this.state.childName}`);
  }

  render() {
    const { childName } = this.state;
    let mainChildOutput = <span>{childName}</span>;
    return (
      <div>
        <div>### Child ####</div>
        <strong>Name</strong>: {mainChildOutput}
      </div>
    );
  }
}

App.tsx is set up like the following -- this is where default value comes in on props

import * as React from 'react';
import { DataLoader } from './DataLoader.tsx';
import './style.css';

export default function App() {
  return (
    <div>
      <DataLoader name={'default value'} />
    </div>
  );
}

CodePudding user response:

You're seeing two different values because you're tracking two different states. One in the parent component and one in the child component.

Don't duplicate data.

If the child component should always display the prop that's passed to it then don't track state in the child component, just display the prop that's passed to it. For example:

export class Grid extends React.Component<PropsParams, {}> {
  render() {
    const { childName } = this.props; // <--- read the value from props, not local state
    let mainChildOutput = <span>{childName}</span>;
    return (
      <div>
        <div>### Child ####</div>
        <strong>Name</strong>: {mainChildOutput}
      </div>
    );
  }
}

CodePudding user response:

In the Child component, you set the prop childName value to state in the contructor ONLY. The constructor is executed ONLY WHEN THE COMPONENT IS MOUNTED. So, it doesn't know if the childName prop is changed later.

There are 2 solutions for this.

(1) Directly use this.props.childName without setting it to a state.

(2) Add a useEffect that updates the state value on prop change.

React.useEffect(() => {
  this.state = {
    childName: this.props.childName;
  };
}, [this.props.childName]);

However, I recommend 1st solution since it's not a good practice to duplicate data.

  • Related