0

I have trouble with simple task of adding elements selected in checkboxes to an array in component state. It seems like the push method for state.toppings (Editor.js) is invoked twice for each checkbox click, even though console.log shows that updateFormValueCheck method is invoked once per click. Can anyone help?

This is App.js

import React, { Component } from "react";
import { Editor } from "./Editor";
import { Display } from "./Display";
export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      formData: {}
    }
  }
  submitData = (newData) => {
    console.log("newData", newData)
    this.setState({ formData: newData });
  }
  render() {
    return <div className="container-fluid">
      <div className="row p-2">
        <div className="col-6">
          <Editor submit={this.submitData} />
        </div>
        <div className="col-6">
          <Display data={this.state.formData} />
        </div>
      </div>
    </div>
  }
}

This is Editor.js

import React, { Component } from "react";
export class Editor extends Component {
    constructor(props) {
        super(props);
        this.state = {
            toppings: ["Strawberries"]
        }
        this.toppings = ["Sprinkles", "Fudge Sauce",
            "Strawberries", "Maple Syrup"]
    }

    updateFormValueCheck = (event) => {
        event.persist();
        this.setState(state => {
            if (event.target.checked) {
                state.toppings.push(event.target.name);
            } else {
                let index = state.toppings.indexOf(event.target.name);
                state.toppings.splice(index, 1);
            }
        }, () => this.props.submit(this.state));
    }
    render() {
        return <div className="h5 bg-info text-white p-2">
            <div className="form-group">
                <label>Ice Cream Toppings</label>
                {this.toppings.map(top =>
                    <div className="form-check" key={top}>
                        <input className="form-check-input"
                            type="checkbox" name={top}
                            value={this.state[top]}
                            checked={this.state.toppings.indexOf(top) > -1}
                            onChange={this.updateFormValueCheck} />
                        <label className="form-check-label">{top}</label>
                    </div>
                )}
            </div>
        </div>
    }
}

This is Display.js

import React, { Component } from "react";

export class Display extends Component {
    formatValue = (data) => Array.isArray(data)
        ? data.join(", ") : data.toString();
    render() {
        let keys = Object.keys(this.props.data);
        if (keys.length === 0) {
            return <div className="h5 bg-secondary p-2 text-white">
                No Data
            </div>
        } else {
            return <div className="container-fluid bg-secondary p-2">
                {keys.map(key =>
                    <div key={key} className="row h5 text-white">
                        <div className="col">{key}:</div>
                        <div className="col">
                            {this.formatValue(this.props.data[key])}
                        </div>
                    </div>
                )}
            </div>
        }
    }
}

The output is: enter image description here

Bartek
  • 49
  • 6

1 Answers1

1

You cannot directly mutate this.state, it can only be done using this.setState. For more info. refer this: Why can't I directly modify a component's state, really?

Therefore, you need to update your Editor component as follows.

componentDidMount is used to display the initial state during the initial rendering. Then componentDidUpdate is used to render the state changes through display component whenever it's updated.

import React, { Component } from "react";
export class Editor extends Component {
  constructor(props) {
    super(props);
    this.state = {
      toppings: ["Strawberries"],
    };
    this.toppings = ["Sprinkles", "Fudge Sauce", "Strawberries", "Maple Syrup"];
  }

  updateFormValueCheck = (event) => {
    event.persist();

    let data;

    if (event.target.checked) {
      data = [...this.state.toppings, event.target.name];
    } else {
      const index = this.state.toppings.indexOf(event.target.name);
      const temp = [...this.state.toppings];
      temp.splice(index, 1);
      data = temp;
    }

    this.setState({
      toppings: data,
    });
  };

  componentDidMount() {
    this.props.submit(this.state.toppings);
  }

  componentDidUpdate(prevPros, prevState) {
    if (prevState.toppings !== this.state.toppings) {
      this.props.submit(this.state.toppings);
    }
  }

  render() {
    console.log(this.state);
    return (
      <div className="h5 bg-info text-white p-2">
        <div className="form-group">
          <label>Ice Cream Toppings</label>
          {this.toppings.map((top) => (
            <div className="form-check" key={top}>
              <input
                className="form-check-input"
                type="checkbox"
                name={top}
                value={this.state[top]}
                checked={this.state.toppings.indexOf(top) > -1}
                onChange={this.updateFormValueCheck}
              />
              <label className="form-check-label">{top}</label>
            </div>
          ))}
        </div>
      </div>
    );
  }
}

Hope this would be helpful to solve your issue.

Kavindu Vindika
  • 2,449
  • 1
  • 13
  • 20