Why does it check multiple boxes?

55 Views Asked by At

Hello guys I am trying to create checkboxes in next js that loop through each filter and then every option. Now I got that working. I am facing an issue with checking them as every time I check an input of an "option" from a certain filter it checks all the other options with same index in other filters. I am using state to manage the check boxes. Basically, I initialized an array of object that contain a checked boolean and a value string. Please see if I can me fix the issue of checking multiple options when only pressing on one.

Product-filters.jsx:

"use client";
import React, { useState } from "react";
import { styled } from "styled-components";
import { useRouter, useSearchParams } from "next/navigation";
import Checkbox from "./ui/Checkbox";
export default function Productfilters() {
  const filters = [
    {
      id: "category",
      name: "Category",
      options: [
        { value: "bags", label: "Bags" },
        { value: "belts", label: "Belts" },
        { value: "gloves", label: "Gloves" },
        { value: "scarves", label: "Scarves" },
        { value: "wallets", label: "Wallets" },
      ],
    },
    {
      id: "size",
      name: "Size",
      options: [
        { value: "xs", label: "X-Small" },
        { value: "s", label: "Small" },
        { value: "m", label: "Medium" },
        { value: "l", label: "Large" },
        { value: "xl", label: "X-Large" },
        { value: "one-size", label: "One Size" },
      ],
    },
    {
      id: "color",
      name: "Color",
      options: [
        { value: "black", label: "Black" },
        { value: "blue", label: "Blue" },
        { value: "brown", label: "Brown" },
        { value: "green", label: "Green" },
        { value: "yellow", label: "Yellow" },
      ],
    },
  ];
  const initialCheckedState = filters.reduce((acc, filter) => {
    filter.options.forEach((option) => {
      acc.push({ checked: false, value: option.value });
    });
    return acc;
  }, []);
  const [checkedState, setCheckedState] = useState(initialCheckedState);
  console.log(checkedState);

  console.log(checkedState);
  const [isOpen, setIsOpen] = useState(false);
  const toggleContent = () => {
    setIsOpen(!isOpen);
  };

  function handleOnChange(position, value) {
    const updatedCheckedState = [...checkedState]; // Create a copy of the state

    for (const item of updatedCheckedState) {
      if (item.value === value) {
        item.checked = !item.checked;
        break; // Exit the loop once the item is found and updated
      }
    }

    setCheckedState(updatedCheckedState);
  }

  const Filter = styled.h2`
    color: white;
    justify-content: end;
  `;
  const FiltersWrapper = styled.div`
    display: flex;
    justify-content: right;
    margin-right: 40px;
    cursor: pointer;
  `;
  const FilterBar = styled.div`
    width: ${({ isOpen }) => (isOpen ? "250px" : "0px")};
    transition: ease-in-out 0.3s;
    height: 100vh;
    z-index: 4;
    background-color: #c9c90c;
    z-index: 1;
    position: fixed;
    right: 0;
    top: 0;
  `;
  const FilterHeader = styled.h1`
    font-size: xx-large;
  `;
  const FilterCategory = styled.div`
    border-bottom: 1px solid gray;
  `;
  const FilterCategoryContent = styled.div`
    display: flex;
    justify-content: space-between;
  `;
  const FilterName = styled.h1``;
  const Filters = styled.div`
    display: flex;
    flex-direction: column;
  `;
  const HiddenCheckbox = styled.input.attrs({ type: "checkbox" })`
    position: absolute;
    opacity: 0;
    pointer-events: none;
  `;

  const CustomCheckbox = styled.span`
    width: 1.2em;
    height: 1.2em;
    border: 2px solid #333;
    background-color: ${({ isChecked }) =>
      isChecked ? "#333" : "transparent"};
    border-radius: 3px;
    transition: background-color 0.2s;
    margin-right: 0.5rem;
  `;
  const FilterContainer = styled.div`
    display: flex;
    justify-content: space-between;
  `;
  const Wrapper = styled.div`
    height: 100%;
    width: 100%;
  `;
  return (
    <Wrapper>
      <FilterBar isOpen={isOpen}>
        <svg
          onClick={toggleContent}
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          class="w-6 h-6"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M6 18L18 6M6 6l12 12"
          />
        </svg>
        <FilterHeader>Filter</FilterHeader>
        {filters.map((section, i) => (
          <FilterCategory key={i}>
            <FilterCategoryContent>
              <FilterName>{section.name}</FilterName>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                stroke-width="1.5"
                stroke="currentColor"
                class="w-6 h-6"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  d="M12 6v12m6-6H6"
                />
              </svg>
            </FilterCategoryContent>
            <Filters>
              {section.options.map((option, optionIdx) => (
                <FilterContainer key={optionIdx}>
                  <input
                    type="checkbox"
                    id={`custom-checkbox-${option.value}`}
                    checked={checkedState[optionIdx].checked}
                    onChange={() => handleOnChange(optionIdx, option.value)}
                  />
                  {option.label}
                </FilterContainer>
              ))}
            </Filters>
          </FilterCategory>
        ))}
      </FilterBar>
      <FiltersWrapper>
        <Filter onClick={toggleContent}>Filters</Filter>
      </FiltersWrapper>
    </Wrapper>
  );
}

Thank you in advance :)

1

There are 1 best solutions below

6
episch On BEST ANSWER

The issue you're facing with your checkboxes is because you're using the optionIdx as the index to access and update the checkedState. The index is only unique within each filter of category, so when you changes the state in one category, it affects the checkboxes with the same index in other categories. To fix this issue, you should use a unique identifier for each checkbox.

One way to do this is to use a combination of the filter id and the option value as the identifier. Here's how you can modify your code to achieve this:

// ...
function handleOnChange(filterId, optionValue) {
  const updatedCheckedState = [...checkedState]; // Create a copy of the state

  const itemIndex = updatedCheckedState.findIndex(
    (item) => item.value === optionValue && item.filterId === filterId
  );

  if (itemIndex !== -1) {
    updatedCheckedState[itemIndex].checked = !updatedCheckedState[itemIndex].checked;
    setCheckedState(updatedCheckedState);
  }
}

// ...

{section.options.map((option, optionIdx) => (
  <FilterContainer key={optionIdx}>
    <input
      type="checkbox"
      id={`custom-checkbox-${section.id}-${option.value}`}
      checked={checkedState.some(
        (item) => item.value === option.value && item.filterId === section.id
      )}
      onChange={() => handleOnChange(section.id, option.value)}
    />
    {option.label}
  </FilterContainer>
))}

In this modified code, we use the filterId and option.value to identify each checkbox uniquely in the handleOnChange function. We also update the checkedState array accordingly. This should resolve the issue of checkboxes affecting others in different filter categories.