alt-rock
alt-rock

Reputation: 357

REACT: concatenate stateful objects to array

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>
    )}

Upvotes: 0

Views: 101

Answers (3)

lankovova
lankovova

Reputation: 1426

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.

Upvotes: 1

Chase DeAnda
Chase DeAnda

Reputation: 16441

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')
);

Upvotes: 2

user2652134
user2652134

Reputation:

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')
);

Upvotes: 1

Related Questions