javascriptreactjsreact-hooksonclickonblur

Switching between product category cards


I have cards with categories, clicking on which opens hidden content. I need the card to close when switching between them and if a click occurs behind the content.

import React, { useRef } from "react";
import s from "./Shop.module.css";
import { useState } from "react";

export const Shop = () => {
  const card = [
    {
      name: "Brands",
      cat: ["Adidas", "Nike", "Reebok", "Puma", "Vans", "New Balance"],
      show: false,
      id: 0,
    },
    {
      name: "Size",
      cat: ["43", "43,5"],
      show: false,
      id: 1,
    },
    {
      name: "Type",
      cat: ["Sneakers ", "Slippers"],
      show: false,
      id: 2,
    },
  ];
  const [active, setActive] = useState({});
  const handleActive = (id) => {
    setActive({ ...active, [id]: !active[id] });
  };
  const handleDisable = (index) => {
    setActive(index);
  };

  return (
    <div className={s.container}>
      <div className={s.brandInner}>
        {card.map((i, index) => {
          return (
            <div className={s.brandCard} key={i.id}>
              <button
                className={`${s.brandBtn} `}
                onClick={() => handleActive(i.id, index)}
                onBlur={() => handleDisable(index)}
              >
                <p className={`${active[i.id] ? `${s.brandBtnActive}` : ``}`}>
                  {i.name}
                </p>
              </button>
              <div
                className={`${s.openCard} ${active[i.id] ? "" : `${s.dNone}`}`}
              >
                <ul className={s.brandList}>
                  {i.cat.map((elem) => {
                    return (
                      <li key={elem} className={s.brandItem}>
                        {elem}
                      </li>
                    );
                  })}
                </ul>
                <button className={s.brandOpenBtn}>Apply</button>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

I tried to do it through onBlur, but this way I can't interact with the content that appears when opening the card, please help


Solution

  • You could do this a few different ways, here are two.

    Array version

    You can do this by using a array to keep track of the ids that are active.

    const [active, setActive] = useState([]);
    

    For the event handler we will creata a new toggleActive function which replaces the others. This will check if the id is already in the array and if so remove it, else add it.

    const toggleActive = (id) => {
      setActive((prevActive) => {
        if (prevActive.includes(id)) {
          return prevActive.filter((activeId) => activeId !== id);
        }
        return [...prevActive, id];
      });
    };
    

    Then in the return of the component we need to updated some logic as well. Then handlers only take in the id of the i. To check if the id is in the array with can use includes.

    <button
      className={s.brandBtn}
      onClick={() => toggleActive(i.id)}
    >
      <p className={`${active.includes(i.id) ? s.brandBtnActive : ""}`}>
        {i.name}
      </p>
    </button>
    <div
      className={`${s.openCard} ${active.includes(i.id) ? "" : s.dNone}`}
    >
    

    Object version

    This version is to do it with a object.

    const [active, setActive] = useState({});
    

    The handler, this will toggle the value of the id starting with false if there is no value for the id yet.

    const toggleActive = (id) => {
      setActive((prevActive) => {
        const prevValue = prevActive[id] ?? false;
        return {
          ...prevActive,
          [id]: !prevValue,
        };
      });
    };
    

    The elements

    <button
      className={s.brandBtn}
      onClick={() => toggleActive(i.id)}
    >
      <p className={`${active[i.id] ? s.brandBtnActive : ""}`}>
        {i.name}
      </p>
    </button>
    <div
      className={`${s.openCard} ${active[i.id] ? "" : s.dNone}`}
    >
    

    Edit: toggle with closing others

    First we declare the state with a initial value of null

    const [active, setActive] = useState(null)
    

    We create the toggleActive function which checks if the id to toggle is the previous id, if so return null else return the new active id

      const toggleActive = (id) => {
        setActive((prevActive) => {
          if (prevActive === id) return null;
          return id;
        });
      };
    

    For the rendering it is quite simple, add the toggleActive function to the button and check if the active is the same id

    <button
      className={s.brandBtn}
      onClick={() => toggleActive(i.id)}
    >
      <p className={`${active === i.id ? s.brandBtnActive : ""}`}>
        {i.name}
      </p>
    </button>
    <div
      className={`${s.openCard} ${active === i.id ? "" : s.dNone}`}
    >