2

I've been using this method to render multiple custom elements from an array but this is my first time doing it using a map data structure. It compiles fine but renders nothing. I've set up a codesandbox here.

import "./styles.css";
import React from "react";
import ImageGrid from "./ImageGrid";

interface OnlineImage {
  id: string;
  url: string;
}

export default function App() {
  const [onlineImages, setOnlineImages] = React.useState<Map<string, OnlineImage[]>>();
  
  React.useEffect(() => {
    let onlineImageMap = new Map<string, OnlineImage[]>();

    // fake api call
    onlineImageMap.set("randomImageSet1", [
      { id: "1", url: "https://picsum.photos/200" },
      { id: "2", url: "https://picsum.photos/200" }
    ]);
    onlineImageMap.set("randomImageSet2", [
      { id: "1", url: "https://picsum.photos/200" },
      { id: "2", url: "https://picsum.photos/200" }
    ]);

    // set state
    setOnlineImages(onlineImageMap);
  }, []);

  return (
    <div className="App">
      <div>Below should render my custom ImageGrid for each item in map...</div>
      <>
        {onlineImages?.forEach((imageSet, category) => {
          return (
            <>
              <div>Image Category: {category}</div>
              <ImageGrid images={imageSet} />
            </>
          );
        })}
      </>
    </div>
  );
}
Samuel Zrna
  • 149
  • 1
  • 1
  • 9

2 Answers2

3

Hi Samuel: I think you should first convert the map to an Array and then use the Array.prototype.map method which actually returns a copy of the original array with your function applied to it.

As you probably already figured out the return statement does nothing within a forEach function (to be more exact it only stops the execution of the code but does not bring back anything into the outer context).

If you really want to use forEach you'll have to use an array or Object to catch it then use Object.entries/.map to iterate over it


const myMap = new Map(Object.entries({a: 1, b: 2}))
const myMapCaptureList = []
myMap.forEach((value, key) => {
myMapCaptureList.push([key, value])
}
// then you can use myMapCaptureList
myMapCaptureList.map(([key, value]) => <div>{key}: <span>{value}</span></div>);

But I would suggest that it is much easier to use the very helpful Array.from method that can help convert a Map into an array of key/value pair entries: [[key1, val1], [key2, val2]]. It is essentially equivalent to running Object.entries(someObject).

{Array.from(onlineImages || []).map(([category, imageSet]) => {
          return (
            <>
              <div>Image Category: {category}</div>
              <ImageGrid images={imageSet} />
            </>
          );
        })}
Zargold
  • 1,892
  • 18
  • 24
  • Zargold, thanks for the answer, but how do you access the key/value pair using your method? In my code sandbox it doesn't work that way. – Samuel Zrna Mar 23 '22 at 17:32
  • https://codesandbox.io/embed/react-typescript-forked-31cxeh?fontsize=14&hidenavigation=1&theme=dark Just tried to fork yours it worked, may have had the category vs imageSet order incorrect (but they key is that when you run Array.from(aMap) it will return Object.entries of that Map as the first argument so you can destructure the first argument in the map that comes from `Array.from` using: `([key, val])` so: `Array.from(myMap).map(([key, val]) =>
    {key}: {val}
    )`
    – Zargold Mar 23 '22 at 17:36
  • This works perfectly as I needed it to. Thank you so much! – Samuel Zrna Mar 23 '22 at 17:40
1

You are using the .forEach method - which returns nothing, instead use .map that is identical but does return things

 {onlineImages?.map((imageSet, category) => 
        <>
          <div>Image Category: {category}</div>
          <ImageGrid images={imageSet} />
        </>
    )}
polisen
  • 275
  • 1
  • 7
  • 1
    Only problem is I'm pretty sure that: `Map` does not have `.map` method on its prototype. `VM16144:1 Uncaught TypeError: (intermediate value).map is not a function at :1:31` – Zargold Mar 23 '22 at 17:25