Hello! My question is very simple and affects the basic thing - rendering elements on the page. But to my surprise, I have not been able to find information on this issue for a long time.

I use ReactJS as a front-end framework.

Let's say I have a page with the simplest form: a field, the Add Field button and a Submit button. Attention a question: how to implement this button Add a field? When you click on that - another text field appears.

I'll tell you how I tried to solve this problem.

class IngredientForm extends Component { render() { return( <form onSubmit={this.handleSubmit.bind(this)}> <input type="text"/> { this.renderOtherInputs() } <input type="button" value="Add Ingredient" onClick={this.addIngredient.bind(this)}> <input type="submit" value="Submit Form"> </form> ); } } 

Here is the code for this form itself. When you click on the Add Ingredient button, another <input type="text> should appear.

How to do it? I know that the basis of Meteor is reactivity, so I don’t need to render anything directly. You need to rely on some kind of reactive data source and change it. And it will already cause changes to the interface.

In tutorials only one situation was considered - we have a collection, we need to display each document from the collection. That is, the reactive data source is a collection, we collapse it into an array, and then we draw an interface for each element of the array. Well, I decided to do likewise, but since the number of inputs on the page is a local state on the client, it will be wrong to store it in the collection. meteor has the answer - reactive var. we have:

 numOfIngredients = new ReactiveVar([]); 

When we click on the Add field, the following method works:

 addIngredient(e) { e.preventDefault(); let newNumOfIngredients = numOfIngredients.get(); newNumOfIngredients.push('no matter'); numOfIngredients.set(newNumOfIngredients); } 

Well, the method for rendering based on our jet array:

 renderOtherInputs() { return numOfIngredients.get().map((elem) => { return( <input type="text"/> ); } } 

But the code does not work. I registered in the addIngredient console.log(numOfIngredients.get()) method console.log(numOfIngredients.get()) and when I clicked on the add field button, the correct behavior was output to the console:

 ['no matter'] ['no matter','no matter'] ['no matter','no matter','no matter'] ... 

However, the field does not appear. I tried and with createContainer to do, declaring numOfIngredients = new ReactiveVar([]); there, and then referring to it as this.props.numOfIngredients.get() . The array is filled, the console is displayed, input does not appear. Perhaps my decision is inherently wrong and crutch.

question in English

I really hope for the help of the community, especially since I have a simple question, as it seems to me. Thanks in advance for your answers!

    1 answer 1

    The task has been solved.

    So. Absolutely correct was the assumption that reactivity is the key to solving the problem. But whose reactivity? I naively suggested that the meteor, and therefore used his tool Reactive Var. And here lies the mistake! ReactJS has its own means for making reactive changes and (by all appearances) is not at all friendly with Reactive Vars and ReactiveDicts.

    It is necessary to use state React component. State is essentially an object, so we call the field as we like and we will store the type of data we need, in our case = array. You need to set the initial state value in the component class constructor:

     constructor(props) { super(props); this.state = { inputs: [], } } 

    We set the Inputs field, which at the time of component rendering will be an empty array.

    Now we will write the function of drawing additional inputs, relying on the state.inputs:

     renderOtherInputs() { return this.state.inputs.map( (each, index) => { return ( <input key={ index } type="text" /> ); }); } 

    And write a function to add a new field. To add a new input on the screen when adding a new element to the array, we need to interact with the state.inputs not directly, but using the setState function, which provides reactivity:

     addInput(e) { e.preventDefault(); var temp = this.state.inputs; temp.push('no matter'); this.setState({ inputs: temp, }); } 

    Done!

    ps I am upset that I did not get an answer. Not in finished form, but at least one-word syllable: state. In all the docks I read that a meteor, and react in particular, on stackoverflow have a huge community ... and here you are, with an elementary question to help laziness

    ps.2. I fought half an hour later and wrote a function to remove a specific input:

     deleteIngredient(e) { e.preventDefault(); let index = e.target.getAttribute('id'); let temp = this.state.inputs; delete temp[index]; this.setState({ inputs: temp, }); } 

    I know that delete creates a hole in the array, but using temp.splice(index,1) does not work. The input is deleted, but always only the last one below. While the desired element is deleted in the array.

    Next to the input, respectively, you need a button with onClick= {this.deleteIngredient.bind(this)}

    ps.3 of course it's nice to figure it out myself :) and I hope at least someone has helped