4

Lets say I have a class with a state level array

ElementsClass = React.createClass({
    getInitialState: function() {
      return {
          elements: []
      }
    },
    addElement: function() {
        var element = {
            name: ""
        };
    },
    render() {
        return (
            {this.state.elements.map(function (element, i) {
                    return <input value={element.name} />
                }
            )}
        )
}

The idea being that I can dynamically add to the elements array and have a new input field appearing.

How do I bind the data so that I am able to change the value in the input field and have that reflect automatically in the correct element in the elements array?

3 Answers 3

2

To dynamically sync your inputs with your state array you can use someting called linkState from the react-catalyst package. Once you've installed it with npm you can use it in the following way:

 //need to import
import Catalyst from 'react-catalyst'; 

ElementsClass = React.createClass({
    // mixin the linkedstate component
    mixins : [Catalyst.LinkedStateMixin],       

    getInitialState: function() {
      return {
          elements: []
      }
    },
    addElement: function() {
        var element = {
            name: ""
        };

        //add to elements array
        this.state.elements.push(element);

        //let react know to rerender necessary parts
        this.setState({
            elements : this.state.elements
        });
    },
    render() {
        return (
            {this.state.elements.map(function (element, i) {
                    //use the linkState method
                    return <input valueLink={this.linkState('elements.'+i+'.name')} />
                }
            )}
        )
}

The reason we need the react-catalyst package is that natively React's valueLink will only link top level state items, in your case elements. Obviously this isn't particularily useful but thankfully it's a fairly easy problem to solve.

Note: for iterated items like your element inputs, you need to provide a unique key. Something like the following (might need modifying to be more specific):

{this.state.elements.map(function (element, i) {
        //use the linkState method
        return <input valueLink={this.linkState('elements.'+i+'.name')} key={'elinput' + i} />
    }
)}

This doesn't have any outward effect on your app, it's mostly to help react target the element internally.

Sign up to request clarification or add additional context in comments.

2 Comments

Sorry, I don't think I clarified the question fully. When adding the value={element.name} to the input field, the input field cannot be modified. How can I add an onChange method that correctly targets the relevant entry in the elements array
@Pram ah, I completely misunderstood. That is actually a bit more tricky. You have to use valueLink instead of value but React only allows that for top level state values. I'll update my answer, but you'll need a a library like react-catalyst to do the two way binding that way.
1

If you want to do this with just ES5 and React, one solution would be this:

var ElementsClass = React.createClass({

getInitialState: function() {
  return {
      elements: []
  }
},
createElement: function(){
  var element = {
    name: ''
  };
  this.setState({elements: this.state.elements.concat(element)});
},
updateElement: function(pos, event) {
    var value = event.target.value;
    var updatedElements = this.state.elements.map(function(element, i){
      if (i === pos){
        return {name: value};
      }
      return element;
    });
    this.setState({elements: updatedElements});
},
render: function() {
    console.log(this.state.elements);
    return (
      <div>
        {this.state.elements.map(function (element, i) {
           var boundClick = this.updateElement.bind(this, i);
           return <input key={i} onKeyUp={boundClick}/>
        }.bind(this))}
        <button onClick={this.createElement}>Add Element</button>
      </div>
    )
}
});

React.render(<ElementsClass />, document.getElementById('app'));

You want to treat component state as immutable, so you don't want to call a mutating method like push on elements.

Comments

0

These situations are handled easily with custom links packages.

State and Forms in React, Part 3: Handling the Complex State

import Link from 'valuelink';

// linked inputs will be deprecated, thus we need to use custom wrappers 
import { Input } from 'valueLink/tags.jsx'

const ElementsClass = React.createClass({
    getInitialState: function() {
      return {
          elements: []
      }
    },

    render() {
        // Take link to the element
        const elementsLink = Link.state( this, 'elements' );

        return (
            <div>
                { elementsLink.map( ( elementLink, i ) => (
                     <Input key={ i } valueLink={ elementLink.at( 'name' ) } />  
                ))}

                <button onClick={ elementsLink.push({ name : '' })}>
                    Add Elements
                </button>
            </div>
        );
    }
});

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.