Harry
Harry

Reputation: 93

How to show category name once if its already rendered within a map function?

Basically, I have a bunch of categories that pharmaceutical drugs are classed in. I also have individual drugs that have a class.

Right now, my code basically maps all categories from the category array and if the class of the drug matches the category, it places it underneath it.

However, I only want to render a category in the drop-down if drug.class matches it.

tldr: drug.class doesn't match a category? then don't show the category in the dropdown. drug.class matches a category? render the category and place the drug underneath that category, however, do NOT repeat the category for every drug.

Basically this rendering ONLY if drug.class matches the category.name, so I don't have empty categories with no drugs underneath.

        <ListSubheader key={category.id}>{category.name}</ListSubheader>

I hope I've explained this to the best of my ability, here's my code:

 {categories.map(category => 
   (<span>
        <ListSubheader key={category.id}>{category.name}</ListSubheader>
        {drugs.map(drug => drug.class.toLowerCase()===category.name.toLowerCase() ? 
        <>
        { patient.age <= 12 && drug.suggestedDosePediatric != "Restricted" &&
        <MenuItem key={drug._id} onClick={()=>handleClick(drug)} value={drug.value}>{drug.name}</MenuItem>
        }
         { patient.age > 12 &&
        <MenuItem key={drug._id} onClick={()=>handleClick(drug)} value={drug.value}>{drug.name}</MenuItem>
        }
        </>
         : null)}
   </span>)
)}

Upvotes: 0

Views: 206

Answers (2)

3limin4t0r
3limin4t0r

Reputation: 21110

You could group the drugs by class.

// assuming the following helper
function groupBy(iterable, keyFn) {
  const groups = new Map();
  for (const item of iterable) {
    const key = keyFn(item);
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(item);
  }
  return groups;
}

With the above helper (or something similar from a library) we can group the drugs and then iterate over them.

if (patient.age <= 12) {
  drugs = drugs.filter(drug => drug.suggestedDosePediatric != "Restricted");
}

const drugsByCategoryName = groupBy(drugs, drug => drug.class.toLowerCase());

// ...

{Array.from(drugsByCategoryName, ([categoryName, drugs]) => (
  <span>
    <ListSubheader>{categoryName}</ListSubheader>
    {drugs.map((drug) => (
      <MenuItem key={drug._id} onClick={()=>handleClick(drug)} value={drug.value}>
        {drug.name}
      </MenuItem>
    ))}
  </span>
))}

The above essentially answers your question. Although you might have noticed that the category is currently only a name (key={category.id} is missing). To get access to all properties of a category it's a good idea to create a lookup object for categories first, since you'll have to find the category belonging to each drug.

const categoriesByName = new Map(categories.map((category) => (
  [category.name.toLowerCase(), category]
)));

Then instead of grouping by category name, group the drug by the category object.

const drugsByCategory = groupBy(drugs, (drug) => (
  categoriesByName.get(drug.class.toLowerCase()))
));

This new definition of drugsByCategory is grouped by category object, not just the name. Which allows you access to the category properties when iterating over it.

{Array.from(drugsByCategoryName, ([category, drugs]) => (
  <span>
    <ListSubheader key={category.id}>{category.name}</ListSubheader>
    {/* ... */}
  </span>
))}

There is still a small "loophole" within the above code that could result in an exception. What if the list of drugs includes a drug.class that is not present within the categories? Such a scenario might not exist, in which case you don't have to worry about it. If this scenario does exist the above code already handles it pretty well. categoriesByName.get(drug.class.toLowerCase())) will return undefined, meaning that all drugs with a non-existent drug.class are listed under the undefined key within the drugsByCategory Map.

This allow for different solutions. You could ignore all drugs with a non-existent drug.class by removing the group from the collection.

drugsByCategory.delete(undefined);

Another option would be to add an "others" category at the end.

const others = drugsByCategory.get(undefined);
drugsByCategory.delete(undefined);

// ...

{Array.from(drugsByCategory, (category, drugs) => (
   <span>
     <ListSubheader key={category.id}>{category.name}</ListSubheader>
     {/* ... */}
   </span>
))}
{others && (
   <span>
     <ListSubheader key="others">Others</ListSubheader>
     {/* ... */}
   </span>
)}

Upvotes: 1

hellogoodnight
hellogoodnight

Reputation: 2129

drugs.filter(drug => drug.class.toLowerCase() === category.name.toLowerCase()will give you the array with only the drugs you want. You can then map it. They can be chained together, like this:

 {categories.map(category => 
   (<span>
        <ListSubheader key={category.id}>{category.name}</ListSubheader>
        {drugs.filter(drug => drug.class.toLowerCase() === category.name.toLowerCase()).map(drug => drug.class.toLowerCase()===category.name.toLowerCase() ? 
        <>
        { patient.age <= 12 && drug.suggestedDosePediatric != "Restricted" &&
        <MenuItem key={drug._id} onClick={()=>handleClick(drug)} value={drug.value}>{drug.name}</MenuItem>
        }
         { patient.age > 12 &&
        <MenuItem key={drug._id} onClick={()=>handleClick(drug)} value={drug.value}>{drug.name}</MenuItem>
        }
        </>
         : null)}
   </span>)
)}

Upvotes: 0

Related Questions