4

In reactjs I'm trying to render a component from a Map object. More specifically, on the press of a button I create a new "FormRow" component, and I store it in a javascript Map object for convenience (because I'm gonna to use it later on). Whenever it happens, I want to render the new component just added, but I can't figure out how to get it from the Map.

I tried to solve my problem in different ways, using:

  1. myMap.forEach(key => {return myMap.get(key)});
  2. myMap.forEach(component=> {return component});
  3. myMap.values()
  4. myMap.entries()
  5. Object.keys(myMap).map(...)

What I didn't try:

  1. https://stackoverflow.com/a/50183395/9282731 (I don't understand it clearly...)

Here is my code siplified:

FormComposer.js:

  constructor() {
    super();
    this.state = {
      counter: 0,
      myMap: new myMap()
    };

    this.handleCreateNewRow = this.handleCreateNewRow.bind(this);
  }

  /** It returns a new FormRow. */
  handleCreateNewRow() {
    let cloneState = this.state;
    let newRow = (
      <FormRow // <-- the component that I want to render.
        key={this.state.counter}
        rowNumber={this.state.counter}
      />
    );

    cloneState.myMap.set(cloneState.counter, newRow);
    cloneState.counter++; // increment the counter for rows
    this.setState(cloneState);
  }

  render() {
    return (
      <div className="container-fluid m-3">
        <div className="col col-9  float-left">
          <div className="row">
            <div className="text-left">
              <h1>Form Builder</h1>
            </div>
          </div>

          {/* Here is the problem! It automaticly loads all the row created previously */}
          {this.state.myMap.forEach(value => {
            console.log(value); // check

            // it print me what I want, but it doesn't render it...
            return value;
          })}
        </div>
      </div>
    );
  }

The console.log(value) returns:

{$$typeof: Symbol(react.element), type: ƒ, key: "0", ref: null, props: {…}, …}

which is the output that I expected, but I don't know why the render() method doesn't render it. If you change the Map object with an Array, this example works, and the render() method renders to the user what he expects.

Lorenzo
  • 85
  • 1
  • 9
  • You should do this before the return and inside the render, do the calculation there and render it in the return, – Arar May 06 '19 at 15:59

3 Answers3

4

Instead of forEach(), which returns undefined use map() on [...myMap.values()] (which is the values of the Map spread into a new array):

{[...this.state.myMap.values()].map(value => {
    return value;
})}

This will return the values of the entries in myMap. Or, since you don't need to modify the values, the short version:

{this.state.myMap.values()}

Also, be aware, that let cloneState = this.state doesn't actually clone the state, but rather creates a reference to the same object. So you could rewrite your handleCreateNewRow() as:

handleCreateNewRow() {
    const counter = this.state.counter;
    let newRow = (
        <FormRow
            key={counter}
            rowNumber={counter}
        />
    );
    this.state.myMap.set(cloneState.counter, newRow);
    this.setState({counter: counter + 1, myMap});
} 
buboh
  • 897
  • 7
  • 10
  • You're right, I have to pay more attention on `let cloneState = this.state`. I tried your solution: `{this.state.myMap.values().map(value => { return value; })}` but it gives me and error, because myMap.values() returns an iterator instead of an array. – Lorenzo May 06 '19 at 16:26
  • You're right of course, sorry. If you don't need to modify the Map entries, you can just use the short version I provided. Otherwise you could spread the values of the Map into an array: `{[...this.state.myMap.values()].map(...)}`. More info on the [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) `...`. – buboh May 06 '19 at 16:32
  • It works! Thank you a lot! (I suggest you to update the code on your answer, in order to be helpful also to other people that are going to see this post) – Lorenzo May 06 '19 at 16:36
3

I don't see any reason to use a Map when you're using sequential numbers as the key, just an array would make more sense.

But to get all the values from a Map you use its values method:

{this.state.myMap.values()}

Your forEach didn't work because forEach always returns undefined and doesn't do anything with the return value from its callback.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
2

I don't know too much about Maps, but, as another point of view, I would approach this more declarative, so, lets say in the
handleCreateNewRow()
I would do
this.setState({ counter: this.state.counter++, anotherMeta.. })

and then render
var rows = []; for (let i = 0; i < counter; i++) { rows.push(<FormRow key={i} />); } return rows;

Jumper
  • 151
  • 1
  • 14
  • I mixed up all yours three answers @Jumper buboh and T.J.Crowder, I solved my problem using .values() method as buboh and T.J.Crowder suggested, and I cleaned and make more efficient my code using yours and buboh suggestions. Thank you guys! – Lorenzo May 06 '19 at 16:42