AGE
AGE

Reputation: 3792

How to set the state of the child component and the parent component correctly?

In the below example, I am trying to do the following:

I am having problems with the implementation:

TLDR: I believe something is wrong with my use of set state, I am trying to set the state in the child, and in the parent via a callback function. However one is taking priority over the other, both set states are no executing. Why is this and how can implement this correctly?

Note: this is pseudocode, please forgive any typos made, I tried to keep it as real as possible.

class Submit extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const { showButton } = this.props;

        return (
            <button type='button' disabled={!showButton}>
                Hello world!
            </button>
        );
    }

);
class Input extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            name: ''
        };
    }

    handleChange = (e) => {
        const { enable } = this.props;
        const id = e.target.id;
        const value = e.target.value;

        this.setState({
            [id]: value
        });

        enable(id, value); // <- Using this here breaks set state.
    }

    render() {
        const { myName } = this.state;

        return (
            <div>
                <input type='text' id='name' onChange={this.handleChange} placeholder='' />
                <input type='text' id='address' onChange={this.handleChange} placeholder='' />
                <!-- there can be more input fields -->
            </div>
        );
    }
}
class Parent extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            enableButton: false,
            inputFieldValues: {},
        };
    }

    enable = (value) => {
        const { inputFieldValue } = this.state;
        let changes = inputFieldValues || {};

        changes[key] = value;

        this.setState({
            enableButton: true,
            inputFieldValues: changes
        });
    }

    render() {
        const { enableButton } = this.state;

        return (
            <div>
                <Input enable={this.enable} />
                <Submit enableButton={enableButton} />
            </div>
        );
    }
}

Upvotes: 0

Views: 57

Answers (2)

Bogdan Gishka
Bogdan Gishka

Reputation: 484

I'm not quite sure that I've understood your problem, but here is fixed example of you pseudo-code.

What I've noticed:

const { inputFieldValue } = this.state;
let changes = inputFieldValues || {};

changes[key] = value;

I don't know whether you make the same mistake in your real app, but I want to mention that this is pretty rude mistake that can to an unexpected behavior, since you mutate the state object, instead of creating new one. React compares objects references, it doesn't make deep checks of them. This let changes = inputFieldValues || {}; should be let changes = { ...inputFieldValues } || {};.

Also, you didn't provide field values to your inputs. Although, it doesn't affect state changes, but then inputs and the state can become unsynchronized.

class Submit extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { enableButton } = this.props;

    return (
      <button type="button" disabled={!enableButton}>
        Hello world!
      </button>
    );
  }
}

class Input extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      name: "",
      address: ""
    };
  }

  handleChange = e => {
    const { enable } = this.props;
    const id = e.target.id;
    const value = e.target.value;

    this.setState({
      [id]: value
    });

    enable(id, value);
  };

  render() {
    const { myName } = this.state;

    console.log("Input:", this.state);

    return (
      <div>
        <input
          type="text"
          id="name"
          onChange={this.handleChange}
          placeholder=""
          {/* Always provide state values to your inputs to make sure they're always synchronized */}
          value={this.state.name}
        />
        <input
          type="text"
          id="address"
          onChange={this.handleChange}
          placeholder=""
          value={this.state.address}
        />
      </div>
    );
  }
}

class Parent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      enableButton: false,
      inputFieldValues: {}
    };
  }

  enable = (key, value) => {
    const { inputFieldValues } = this.state;
    // Copy objects, when you change their fields.
    let changes = { ...inputFieldValues } || {};

    changes[key] = value;

    this.setState({
      enableButton: true,
      inputFieldValues: changes
    });
  };

  render() {
    const { enableButton } = this.state;

    console.log("Parent:", this.state);

    return (
      <div>
        <Input enable={this.enable} />
        <Submit enableButton={enableButton} />
      </div>
    );
  }
}

ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Upvotes: 1

Oscar Calderon
Oscar Calderon

Reputation: 989

I'm not sure if you have an error in your code and it isn't reflected in the pseudocode you shared here, but based on it i could make it work, having state properly updated in both parent and children component (check the console.log ).

https://codesandbox.io/s/r79q9myj8o

Upvotes: 1

Related Questions