0

I was wonder what I'm doing wrong here. I map through categories and it works perfect. But when I map through sub-categories in dropdown it shows not parent sub-categories, but all items from all categories. For example if I choose category "Developer" in sub categories drop-down I want to show only Front-end, Back-End and Full-Stack options.

setCategory comes from props and the state looks like this:

const [category, setCategory] = useState("");

Code: belox:

<div className="inner--form--row">
    <label htmlFor="category">Category:</label>
    <select id="category" onChange={(e) => setCategory(e.target.value)}>
      <option defaultValue={true}>Choose...</option>
      {categories.map((category) => (
        <option value={category.label} key={category.id}>
          {category.label}
        </option>
      ))}
    </select>
  </div>
  <div className="inner--form--row">
    <label htmlFor="subCategory">Sub Category:</label>

    <select id="category" onChange={(e) => setSubCategory(e.target.value)}>
      <option defaultValue={true}>Choose...</option>
      {categories.map((category) => (
        <>
          {category.subCategories.map((subCat) => (
            <option value={subCat.label} key={subCat.id}>
              {subCat.label}
            </option>
          ))}
        </>
      ))}
    </select>
  </div>

enter image description here

Data looks like this:

categories: [
    {
      id: 1,
      label: "Developer",
      subCategories: [
        {
          id: 2,
          label: "Front-End Developer",
          subCategories: [
            {
              id: 3,
              label: "Junior",
            },
            { id: 4, label: "Mid" },
            {
              id: 5,
              label: "Senior",
            },
          ],
        },
        {
          id: 6,
          label: "Back-End Developer",
          subCategories: [
            { id: 7, label: "Junior" },
            { id: 8, label: "Mid" },
            { id: 9, label: "Senior" },
          ],
        },
        {
          id: 10,
          label: "Full-Stack Developer",
          subCategories: [
            { id: 11, label: "Junior" },
            {
              id: 12,
              label: "Mid",
            },
            {
              id: 13,
              label: "Senior",
            },
          ],
        },
      ],
    },
    ....
3
  • 1. Are you sure of value={category.label} and not value={category.label} ? 2. Can you share your code of setCategory function or please confirm if it is just setting the state? Also can you share you categories data structure? Commented Jun 23, 2022 at 10:28
  • Question updated :) Commented Jun 23, 2022 at 10:44
  • Thanks for the info. I have provided a solution. Commented Jun 23, 2022 at 10:56

3 Answers 3

2
// DATA users.json

[
  {
    "name": "Pietro",
    "category": "Developer",
    "subCategory": "Full-Stack Developer",
    "seniority": "Mid"
  },
  {
    "name": "Ugo",
    "category": "Developer",
    "subCategory": "Full-Stack Developer",
    "seniority": "Mid"
  },
  {
    "name": "Andrea",
    "category": "Developer",
    "subCategory": "Front-End Developer",
    "seniority": "Junior"
  },
  {
    "name": "Gianni",
    "category": "Designer",
    "subCategory": "Graphic Designer",
    "seniority": "Senior"
  }
]

// COMPONENT

import { useEffect, useState } from "react";
import "./styles.css";
import users from './data/users.json'

export default function App() {

  const categories = {
    Developer: {
      subCategories: [
        "Front-End Developer",
        "Back-End Developer",
        "Full-Stack Developer"
      ]
    },
    Designer: {
      subCategories: [
        "Graphic Designer",
        "UX Designer"
      ]
    }
  }
  
  const seniorities = [
    'Junior',
    'Mid',
    'Senior'
  ]

  const [category, setCategory] = useState("");
  const [subCategory, setSubCategory] = useState("");
  const [seniority, setSeniority] = useState("")

  const [filteredUsers, setFilteredUsers] = useState([])

  useEffect(() => {

    const filteredUsers = users.filter( user => 
      (category ? user.category === category : true)
      &&
      (subCategory ? user.subCategory === subCategory : true)
      &&
      (seniority ? user.seniority === seniority : true)
      )

      setFilteredUsers(filteredUsers)

  },[category, subCategory, seniority])

  const handleCategoryChange = (value) => {
    setCategory(value)
    setSubCategory('')
  }

  return (<>
    <div className="App">
      <div className="inner-form-row">

        <label>Category:</label>
        <select id="category" onChange={(e) => handleCategoryChange(e.target.value)}>

          <option value={''}>All</option>
            {Object.keys(categories).map((category, index) => (
              <option value={category} key={index}>
                {category}
              </option>
            ))}

        </select>

        <label>Seniority:</label>
        <select id="seniority" onChange={(e) => setSeniority(e.target.value)}>

          <option value={''}>All</option>
            {seniorities.map((seniority, index) => (
              <option value={seniority} key={index}>
                {seniority}
              </option>
            ))}

        </select>

      </div>
      
      {
        category &&
        <div className="inner--form--row">
          <label htmlFor="subCategory">Sub Category:</label>

          <select id="category" onChange={(e) => setSubCategory(e.target.value)}>

            <option value={''}>All</option>
            {console.log(categories[category])}
            {
              categories[category].subCategories.map((subCat, index) => 
                <option value={subCat} key={index}>
                  {subCat}
                </option>
              )
            }
          </select>

        </div>
      }
      
    </div>
   
    <ul>
      {
        filteredUsers.map( (user, index) => 
          <li key={index}>{user.name}</li>  
        )
      }
    </ul>
 
  </>)
}

sandbox here: https://codesandbox.io/s/compassionate-hill-8cl5px?file=/src/App.js

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you. This is exactly what I need! Just one more question, How I could setCategory setSubCategory and seniority as labels. Because now it saves as objects or arrays. When I try to display it it appears like that. If I save as text values then I'm not able to map through...
1

There are multiple changes required. I am posting a solution with all the things involved.

import { useState } from "react";
import "./styles.css";

export default function App() {
  const [category, setCategory] = useState("");
  const [categories, setSubCategories] = useState([
    {
      id: 1,
      label: "Developer",
      subCategories: [
        {
          id: 2,
          label: "Front-End Developer",
          subCategories: [
            {
              id: 3,
              label: "Junior"
            },
            { id: 4, label: "Mid" },
            {
              id: 5,
              label: "Senior"
            }
          ]
        },
        {
          id: 6,
          label: "Back-End Developer",
          subCategories: [
            { id: 7, label: "Junior" },
            { id: 8, label: "Mid" },
            { id: 9, label: "Senior" }
          ]
        },
        {
          id: 10,
          label: "Full-Stack Developer",
          subCategories: [
            { id: 11, label: "Junior" },
            {
              id: 12,
              label: "Mid"
            },
            {
              id: 13,
              label: "Senior"
            }
          ]
        }
      ]
    }
  ]);
  const [subCategory, setSubCategory] = useState("");

  const handleSetCategory = (e) => {
    const categoryId = e.target.value;
    console.log(categories, categoryId);
    const category = categories.find((cat) => cat.id == categoryId);
    console.log(category);
    setCategory(category);
  };

  return (
    <div className="App">
      <div className="inner--form--row">
        <label htmlFor="category">Category:</label>
        <select id="category" onChange={(e) => handleSetCategory(e)}>
          <option defaultValue={true}>Choose...</option>
          {categories.map((category) => (
            <option value={category.id} key={category.id}>
              {category.label}
            </option>
          ))}
        </select>
      </div>
      <div className="inner--form--row">
        <label htmlFor="subCategory">Sub Category:</label>

        <select id="category" onChange={(e) => setSubCategory(e.target.value)}>
          <option defaultValue={true}>Choose...</option>

          {category &&
            category.subCategories.map((subCat) => (
              <option value={subCat.label} key={subCat.id}>
                {subCat.label}
              </option>
            ))}
        </select>
      </div>
    </div>
  );
}

6 Comments

Hmm, I get this error in console: "Cannot read properties of undefined (reading 'map')" . It happens when it maps here: {category.subCategories.map((subCat) ...
@Get_Date Just add a check category && on the line. This will prevent the error. Cheers! Updated my answer
I really appreciate for you help! But now it shows only "Choose" option and not other sub categories! ...
@Get_Date 1. I missed a param on handleSetCategory(e). 2. Your categoryId value from select is a string so, cat.id == categoryId. Apologies for no testing before.
@Get_Date Please check the updated answer.
|
0

I think that the problem is at this part of the code:

<select id="category" onChange={(e) => setCategory(e.target.value)}>
  <option defaultValue={true}>Choose...</option>
  {categories.map((category) => (
    <>
      {category.subCategories.map((subCat) => (
        <option value={subCat.label} key={subCat.id}>
          {subCat.label}
        </option>
      ))}
    </>
  ))}
</select>

Maybe what you meant to do is this:

<select id="category" onChange={(e) => setCategory(e.target.value)}>
  <option defaultValue={true}>Choose...</option>
  {category.subCategories.map((subCat) => (
    <option value={subCat.label} key={subCat.id}>
      {subCat.label}
    </option>
  ))}
</select>

The way you did you are basically mapping all subcategories to the options indeed, because you going through all categories and then mapping all of its subcategories as options. You already know which is the selected category, so you don't need to loop through all categories again.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.