3
\$\begingroup\$

I'm trying to build a nav, but the way I currently have the data setup I'm having to map within a map with a map to get all of my data out - I have a feeling that this is a poor way of doing something and there may be a better solution?

My code can also be found in a codesandbox here

Both the data and jsx are up for debate and I'm more than happy to modify either or both for a better solution.

My JSX:

    <div className="App">
      <nav>
        {/* Top Level Nav Items */}
        {nav.map((item) => (
          <a href={item.url} key={item.category}>
            {item.category}
          </a>
        ))}
      </nav>

      {/* Nav Sub-Categories */}
      <div className="subCategories">
        {nav.map((item) => (
          <div className="subCategory">
            <img src={item.img} />
            {item.subCatergories.map((subCategory) => (
              <>
                <h3>{subCategory.title}</h3>
                {subCategory.links.map((link) => (
                  <a href={link.url}>{link.name}</a>
                ))}
              </>
            ))}
          </div>
        ))}
      </div>
    </div>

My data:

const nav = [
  {
    category: "LINK NAME",
    url: "/link",
    img: "https://via.placeholder.com/100/?text=Image",
    subCatergories: [
      {
        title: "Sub Category 1",
        links: [
          { name: "link 1", url: "/1" },
          { name: "link 2", url: "/2" },
          { name: "link 3", url: "/3" },
          { name: "link 4", url: "/4" },
          { name: "link 5", url: "/5" },
          { name: "link 6", url: "/6" },
          { name: "link 7", url: "/7" },
          { name: "link 8", url: "/8" },
          { name: "link 8", url: "/9" }
        ]
      },
      {
        title: "Sub Category 2",
        links: [
          { name: "link 1", url: "/1" },
          { name: "link 2", url: "/2" },
          { name: "link 3", url: "/3" },
          { name: "link 4", url: "/4" },
          { name: "link 5", url: "/5" },
          { name: "link 6", url: "/6" },
          { name: "link 7", url: "/7" },
          { name: "link 8", url: "/8" },
          { name: "link 8", url: "/9" }
        ]
      },
      {
        title: "Sub Category 3",
        links: [
          { name: "link 1", url: "/1" },
          { name: "link 2", url: "/2" },
          { name: "link 3", url: "/3" },
          { name: "link 4", url: "/4" },
          { name: "link 5", url: "/5" },
          { name: "link 6", url: "/6" },
          { name: "link 7", url: "/7" },
          { name: "link 8", url: "/8" },
          { name: "link 8", url: "/9" }
        ]
      }
    ]
  },
  {
    category: "LINK NAME 2",
    url: "/link2",
    img: "https://via.placeholder.com/100/?text=Image",
    subCatergories: [
      {
        title: "Sub Category 1",
        links: [
          { name: "link 1", url: "/1" },
          { name: "link 2", url: "/2" },
          { name: "link 3", url: "/3" },
          { name: "link 4", url: "/4" },
          { name: "link 5", url: "/5" },
          { name: "link 6", url: "/6" },
          { name: "link 7", url: "/7" },
          { name: "link 8", url: "/8" },
          { name: "link 8", url: "/9" }
        ]
      },
      {
        title: "Sub Category 2",
        links: [
          { name: "link 1", url: "/1" },
          { name: "link 2", url: "/2" },
          { name: "link 3", url: "/3" },
          { name: "link 4", url: "/4" },
          { name: "link 5", url: "/5" },
          { name: "link 6", url: "/6" },
          { name: "link 7", url: "/7" },
          { name: "link 8", url: "/8" },
          { name: "link 8", url: "/9" }
        ]
      },
      {
        title: "Sub Category 3",
        links: [
          { name: "link 1", url: "/1" },
          { name: "link 2", url: "/2" },
          { name: "link 3", url: "/3" },
          { name: "link 4", url: "/4" },
          { name: "link 5", url: "/5" },
          { name: "link 6", url: "/6" },
          { name: "link 7", url: "/7" },
          { name: "link 8", url: "/8" },
          { name: "link 8", url: "/9" }
        ]
      }
    ]
  }
];

Any help or guidance here would be great, thank you!

\$\endgroup\$
3
  • 1
    \$\begingroup\$ That looks like a perfectly reasonable implementation to me. Nothing stands out as being improvable. \$\endgroup\$ Nov 16 '20 at 15:42
  • \$\begingroup\$ @CertainPerformance Thanks for that! I was told once it wasn't great to loop within loops and it's always stuck with me! Right or wrong! But I'll stick with this if it's alright! Thanks a lot!! \$\endgroup\$
    – Nick
    Nov 16 '20 at 15:48
  • 1
    \$\begingroup\$ Nested loops are a problem when you need an algorithm and the problem is solvable by iterating over the data less frequently. But here, it's not really an algorithm, just rendering data, and, eg, given n total links items, there's no avoiding iterating n times. \$\endgroup\$ Nov 16 '20 at 17:58
1
\$\begingroup\$

The use of nested maps is a fine solution, I just have a few suggestions.

I would rename some keys of your object to make them consistent:

const nav = [
    {
        name: "LINK NAME",
        url: "/link",
        img: "https://via.placeholder.com/100/?text=Image",
        categories: [
            {
                name: "Sub Category 1",
                items: [
                    {name: "link 1", url: "/1"},
                ]
            },
            {
                name: "Sub Category 2",
                items: [
                    {name: "link 1", url: "/1"},
                    {name: "link 2", url: "/2"},
                ]
            },
            {
                name: "Sub Category 3",
                items: [
                    {name: "link 1", url: "/1"},
                    {name: "link 2", url: "/2"},
                    {name: "link 8", url: "/9"}
                ]
            }
        ]
    },
]

I suggest using object destruction in the parameter definition to make the code more concise by avoiding dot notation.

Also, I would create a separate component for subcategories to make the code for navigation simpler to read.

Here is the code for React components:

export default function App() {
    return (
        <div className="App">
            <nav>
                {/* Top Level Nav Items */}
                {nav.map(({ name, url }) => (
                    <a href={url} key={name}>{name}</a>
                ))}
            </nav>

            {/* Nav Sub-Categories */}
            <div className="subCategories">
                {nav.map(({ categories, img }) => (
                    <SubCategory categories={categories} img={img} />
                ))}
            </div>
        </div>
    );
}

function SubCategory({ categories, img }) {
    return (
        <div className="subCategory">
            <img src={img} />
            {categories.map(({ name, items}) => (
                <>
                    <h3>{name}</h3>
                    {items.map(({ name, url }) => (
                        <a href={url}>{name}</a>
                    ))}
                </>
            ))}
        </div>
    )
}
\$\endgroup\$
0
\$\begingroup\$

Although there is nothing wrong with nested maps you are correct to seek a better solution.

Controls nest or compose much cleaner than maps. Allowing controls to have at most one map is a very good practice.
CodeSandbox

export default function LeafLinkList({ links }) {
  return links.map(({name, url}) => <a href={url} key={url}>{name}</a>);
}

export default function SubCategory({ title, links }) {
  return (
    <Fragment>
      <h3>{title}</h3>
      <LeafLinkList links={links} />
    </Fragment>
  );
}

export default function SubCategoryList({ item }) {
  const { subCatergories } = item || {};

  return subCatergories.map(({ title, links }) => (
    <SubCategory key={title} title={title} links={links} />
  ));
}

export default function App() {
  return (
    <div className="App">
      <nav>
        {/* Top Level Nav Items */}
        {nav.map((item) => (
          <a href={item.url} key={item.category}>
            {item.category}
          </a>
        ))}
      </nav>

      {/* Nav Sub-Categories */}
      <div className="subCategories">
        {nav.map((item) => (
          <div className="subCategory" key={item.url}>
            <img src={item.img} alt="box" />
            <SubCategoryList item={item} />
          </div>
        ))}
      </div>
    </div>
  );
}

One of the realities of modern development is constantly changing requirements. Nested or composed controls handles change better. Don't take my word for it. Consider the two solutions and now add the requirement that each Sub Category is collapsible.

How long did it take you to find Sub Category?

Where do you add state?

Here is the new sub Category to handle the new requirement.

export default function SubCategory({ title, links }) {
  const [isOpen, setOpen] = useState(false);
  const indicator = isOpen ? "-" : "+";
  const header = `${indicator} ${title}`;
  const handleClick = () => {
    setOpen(toggleOpen);
  };
  return (
    <Fragment>
      <h3 onClick={handleClick}>{header}</h3>
      {isOpen && <LeafLinkList links={links} />}
    </Fragment>
  );
}

CodeSandbox - add state

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.