21

I had a javascript array that was rendering components using array.map. I switched this array to an es6 Map in order to be able to use key-value pairs to find items more easily, and switched from a .map to a forEach over the Map. Inside the forEach I call a render method that returns a React component, but it isn't being rendered. How do I render a component inside the forEach?

<div className='gallery__items'>
    {resultsByGuid.forEach((result, index) => {
        key++;
        this.renderGalleryItem(result, key);
    })} 
</div>

Here is the renderGalleryItem method:

renderGalleryItem = (item, index) => {
    const { gridItemSelected, itemThumbnailRequested } = this.props;
    return (
        <GalleryItem
            key={index}
            item={item}
            onClick={gridItemSelected}
            fetchThumbnailFunc={itemThumbnailRequested}
        />
    );
};

I understand that forEach doesn't return anything but does that mean I can't render inside it?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
alsoALion
  • 449
  • 1
  • 5
  • 17
  • How about you store/append that html string in a variable that you inject after the forEach? – LukeS Feb 23 '16 at 23:53
  • 1
    Use the [`Map.prototype.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) method. – zerkms Feb 23 '16 at 23:57
  • So, JSX expects an array? Then use `Array.from(resultsByGuid)` and go with `.map` from there. – Bergi Feb 24 '16 at 00:07
  • @John please don't remove the [javascript] tag from JavaScript questions just because they're primarily about a framework or library rather than the language itself. Using the language tag is still totally valid (and useful to people following the tag) even in such cases. Adding framework tags where they're absent is a good change, but removing [javascript] is not. – Mark Amery Jan 31 '21 at 11:40

4 Answers4

18

Just a slight improvement on danday74's example using array destructuring. With options the ES6 Map:

<select>
    {[...options].map(([key, value]) => (
        <option key={key} value={key}>
            {value}
        </option>
    ))}
</select>;
Guilherme Samuel
  • 459
  • 6
  • 11
BrechtVds
  • 553
  • 4
  • 7
  • 1
    This works for me in devtools, but when I try this in my JSX, I'm getting "Uncaught TypeError: options.concat is not a function", but `{Array.from(options.entries()).map(([k, v]) => ([k, v])}` from @Rob M. works for me... probably some babel-plugin missing, eh... – ptim Aug 28 '19 at 16:13
16

You are correct, forEach doesn't return anything, use map instead, it will return an array of JSX components.

Map will allow you to access the key as well: resultsByGuid.map((item, key) => { })

Edit I apologize for jumping the gun and not reading that you were using a Map data structure. forEach won't render anything because you need the return value, you could implement your own Array.map like iterator:

const mapIterator = (map, cb) => {
  const agg = [];
  for(let [key, value] of map) {
    agg.push(cb(value, key));
  }
  return agg;
};

<div className='gallery__items'>
  {mapIterator(resultsByGuid, (result, index) => {
    key++;
    return this.renderGalleryItem(result, key);
  })}
</div>

Edit 2 And thanks to @zerkms for pointing out what should've been obvious to me:

<div className='gallery__items'>
  {Array.from(resultsByGuid.values()).map((result, index) => {
    key++;
    return this.renderGalleryItem(result, key);
  })}
</div>
Rob M.
  • 35,491
  • 6
  • 51
  • 50
  • what is `map` you're referring to? – zerkms Feb 23 '16 at 23:55
  • `Array.map` https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map @zerkms – Rob M. Feb 23 '16 at 23:56
  • `resultsByGuid` is not an array, it's a `Map` object (which does not have a `map` method) – zerkms Feb 23 '16 at 23:57
  • 2
    But it does have an `entries()` method, which you should be able to use in the context of `for` loop... If not, you can always call the `keys()` method on the map... – Robert Rossmann Feb 23 '16 at 23:59
  • 1
    It does have indeed. The `.values()` would be even better. – zerkms Feb 24 '16 at 00:00
  • 2
    `Array.from(resultsByGuid.values()).map(...)` or `[...resultsByGuid.values()].map(...)` – zerkms Feb 24 '16 at 00:13
  • 5
    What am I missing? It seems ridiculously inefficient to have to restructure a Map as an Array every time you want to do any kind of map/reduce/filter function on it. Is the spec not finished? Is there any easy way to write an inline iterator function? – Andy H. Mar 10 '17 at 18:07
  • 1
    Also, this undermines the usage of a Map over Array to preserve order... – MDjava Mar 07 '21 at 06:15
8

another option, where options is an es6 Map() ..

<select>
  {
    [...options].map((entry) => {
      let key = entry[0]
      let value = entry[1]
      return <option key={ key } value={ key }>{ value }</option>
    })
  }
</select>
danday74
  • 52,471
  • 49
  • 232
  • 283
0

If you call .entries() on your map you will get an iterator object which for every key/value pair contains an array with the structure: [key, value] as mentioned here.

So you could just do:

<div className='gallery__items'>
  {resultsByGuid.entries().map((result) => {
    return this.renderGalleryItem(result[1], result[0]);
  })}
</div>

I am still wondering, if there's a simpler solution though.