0

I am trying to create an immutable state array that is dynamically populated by inputs. However, i am unable to create new state and add it to my people array. What is the correct way to do this? Below is a link to a jsfiddle demo of the problem.

LINK TO DEMO: http://jsfiddle.net/560fyjnp/1536/

Mapping and returning appended array:

appendPerson() {
    let newPerson = {
      name: '',
      age: '',
      school: ''
    };

    this.setState((prevState) => ({
      people: prevState.people.concat([newPerson])
    }));
  }

Handling changes from the inputs:

  handleChange(e) {
    this.setState({[e.target.name]: e.target.value})
  }

Example Input:

Name: <input name="name" value={person.name} onChange={this.props.handleChange} />

Mapping Over the state and showing the stateful strings:

    {this.state.people.map((person, index) =>
    <div key={index}>
     Name: {person.name}<br />
     Age: {person.age}<br />
     School: {person.school}
     <hr/>
    </div>
    )}
6
  • Why do you want to append when input is changed? I believe there should be a button or something. Commented Mar 27, 2018 at 20:01
  • there is, see the demo @RoboRobok Commented Mar 27, 2018 at 20:02
  • Your handleChange will not work because you aren't specifying an index in your people array to update. All your passing is {name: 'name'}, but how does your code know which person it should update? Commented Mar 27, 2018 at 20:03
  • @RoboRobok the inputs should manage their own state for each object in the array Commented Mar 27, 2018 at 20:05
  • @ChaseDeAnda how could i fix this to manage each person? Commented Mar 27, 2018 at 20:07

3 Answers 3

2

Working JSFiddle Here

You need to update your handleChange function to accept an index that will be passed in as a param. You should bind the change handler in Inputs to pass in the index:

  handleChange(index, e) {
    // Make a shallow clone of your state array
    const people = [...this.state.people];
    // Update the object at the passed in index with the property name and value
    people[index] = {...people[index], [e.target.name]: e.target.value};
    this.setState({
        people
    });
  }

Full Example:

class Inputs extends React.Component {
  constructor(props) {
    super(props);
    this.state = {}
  }

  render() {
    return (
      <div>
        {this.props.people.map((person, index) =>
            <div key={index}>
          Name: <input name="name" value={person.name} onChange={this.props.handleChange.bind(this, index)} /><br/>
          Age: <input name="age" value={person.age} onChange={this.props.handleChange.bind(this, index)} /><br/>
          School: <input name="school" value={person.school} onChange={this.props.handleChange.bind(this, index)} />
            <hr />
          </div>
         )}
         <button onClick={ this.props.appendPerson }>Add Another Person</button>
      </div>
    );
  }
}

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

    this.state = {
      people: [{
        name: 'Jerry',
        age: '20',
        school: 'Fun University'
      }]
    }

      this.handleChange = this.handleChange.bind(this);
      this.appendPerson = this.appendPerson.bind(this);
  }


  handleChange(index, e) {
    const people = [...this.state.people];
    people[index] = {...people[index], [e.target.name]: e.target.value};
    this.setState({
        people
    });
  }

  appendPerson() {
    let newPerson = {
      name: '',
      age: '',
      school: ''
    };

    //this.setState({ people: this.state.people.concat([newPerson])});
    this.setState((prevState) => ({
      people: prevState.people.concat([newPerson])
    }));
  }

  render() {
    return (
      <div>
        <h1>Add Person:</h1>
        <Inputs
          people={this.state.people}
          appendPerson={this.appendPerson}
          handleChange={this.handleChange}
        />

        <h1>People:</h1>
        {this.state.people.map((person, index) =>
        <div key={index}>
         Name: {person.name}<br />
         Age: {person.age}<br />
         School: {person.school}
         <hr/>
        </div>
        )}
      </div>
    );
  }
}

ReactDOM.render(
  <People/>,
  document.getElementById('container')
);
Sign up to request clarification or add additional context in comments.

5 Comments

Nice answer but using index as a key is an anti-pattern so i believe this answer isn't complete. Definitely should be using a unique ID of the person which will change the way you would implement the handleChange
@Omar right, but no id was set in the example data and all other fields are subject to change, so this was what was left.
@Omar also, using an index as the key only gets you in trouble if the order of the list will be changing frequently. There is not delete in this example, so the order will always be the same.
i know all im saying is its better to go the extra mile and implement it for the questioner and for future viewers. You're answer is good very easy to follow and the questioner can probably do what I'm saying on his own now. Yes theres no delete but i would assume thats something hes going to run into sometime in the future.
@Omar __ noted.
1

You have to pass on the index to handleChange. Fiddle: http://jsfiddle.net/560fyjnp/1551/

class Inputs extends React.Component {
  constructor(props) {
    super(props);
    this.state = {}
  }

  render() {
    return (
      <div>
        {this.props.people.map((person, index) =>
            <div key={index}>
          Name: <input name="name" data-index={index} value={person.name} onChange={this.props.handleChange} /><br/>
          Age: <input name="age" data-index={index} value={person.age} onChange={this.props.handleChange} /><br/>
          School: <input name="school" data-index={index} value={person.school} onChange={this.props.handleChange} />
            <hr />
          </div>
         )}
         <button onClick={ this.props.appendPerson }>Add Another Person</button>
      </div>
    );
  }
}

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

    this.state = {
      people: [{
        name: 'Jerry',
        age: '20',
        school: 'Fun University'
      }]
    }

      this.handleChange = this.handleChange.bind(this);
      this.appendPerson = this.appendPerson.bind(this);
  }


  handleChange(e) {
    this.setState((prevState)=>{
        prevState.people[parseInt(e.target.getAttribute('data-index'))][e.target.name]= e.target.value;
      return prevState;
    })
  }

  appendPerson() {
    let newPerson = {
      name: '',
      age: '',
      school: ''
    };

    //this.setState({ people: this.state.people.concat([newPerson])});
    this.setState((prevState) => ({
      people: prevState.people.concat([newPerson])
    }));
  }

  render() {
    return (
      <div>
        <h1>Add Person:</h1>
        <Inputs
          people={this.state.people}
          appendPerson={this.appendPerson}
          handleChange={this.handleChange}
        />

        <h1>People:</h1>
        {this.state.people.map((person, index) =>
        <div key={index}>
         Name: {person.name}<br />
         Age: {person.age}<br />
         School: {person.school}
         <hr/>
        </div>
        )}
      </div>
    );
  }
}

ReactDOM.render(
  <People/>,
  document.getElementById('container')
);

Comments

1

You need slightly change your code to make this work.

Check out my solution. JSFiddle demo

Here is how onChange function in input elements should ook like.

<input name="name" value={person.name} onChange={({ target }) => this.props.handleChange(index, target.name, target.value)} />

And then there is handleChange function.

handleChange(peopleIndex, name, value) {
    this.setState(prevState => {
      const editedPeople = prevState.people[peopleIndex];
      editedPeople[name] = value;
  
      return {
        people: [
          ...prevState.people.slice(0, peopleIndex),
          editedPeople,
          ...prevState.people.slice(peopleIndex + 1)
        ]
      };
    });
  }

And there you have it. Independent data structures linked with inputs without data-* attribute.

Hope this helps.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.