0

I have created two tabs that when clicked need to show a different set of products and a different set of filters for each selection. My problem is that when I click either tab and call setOptions within changeTab, I need to click each tab twice before it will update 'options', 'options' needs to contain each filter.

Obviously calling setOptions within the click handler is not correct but I can't figure out where or how to correctly update 'options'. Help greatly appreciated.

In the console logs below 'dedupedOptions' updates correctly on click

function filterProducts() {

  const [categoryType, setCategory] = useState("Canine");
  const [activeTabIndex, setActiveTabIndex] = useState(0);
  const {
    productData: {
      products: { products }
    }
  } = useContext(AppContext);

  const productsByCategory = products
    .filter((product) => {
      const { tags } = product;
      return !!tags.find((tag) => tag.includes(categoryType));
    })
    .map((product) => ({
      ...product,
      category: product.tags
        .find((tag) => tag.includes("category:"))
        .split(":")[1]
    }));

  let dedupedOptions = [];

  productsByCategory.forEach((product) => {
    const { tags } = product;
    tags.forEach((tag) => {
      const parts = tag.split(":");
      const key = parts[0];
      const value = parts[1] || null;
      const validTag = tagKeysToDisplay.find(
        (tagKeyToDisplay) => tagKeyToDisplay === key
      );
      if (
        validTag &&
        !dedupedOptions.find((dedupedOption) => dedupedOption.value === value)
      ) {
        dedupedOptions = [
          ...dedupedOptions,
          {
            label: titleCase(value),
            value,
            selected: false
          }
        ];
      }
    });
  });

  const [options, setOptions] = useState(dedupedOptions);
  console.log(dedupedOptions);
  console.log(options);

  const changeTab = (index, category) => {
    setCategory(category);
    setActiveTabIndex(index);
    setOptions(dedupedOptions);
  };

  const setFilter = useCallback(
    (selectedOption) => {
      const optionIsActive = options.find(
        (option) => option.value === selectedOption.value
      )?.selected;
      let newOptions = [];
      newOptions = [
        ...options.map((option) => {
          if (option.value === selectedOption.value) {
            return {
              ...option,
              selected: !optionIsActive
            };
          }
          return option;
        })
      ];
      setOptions(newOptions);
    },
    [options]
  );
}

And the two elements set up as tabs to handle the click events. These are rendered within the same filterProducts function.

      <div className="filter-products__tabs">
        <div
          className={`filter-products__tab
          ${activeTabIndex === 0 ? "is-active" : ""}`}
          onClick={changeTab.bind(this, 0, "Canine")}
        >
          <span>DOG</span>
        </div>
        <div
          className={`filter-products__tab
          ${activeTabIndex === 1 ? "is-active" : ""}`}
          onClick={changeTab.bind(this, 1, "Feline")}
        >
          <span>CAT</span>
        </div>
      </div>
2
  • It's better if you can reproduce that in codesandbox.io Commented Jun 15, 2021 at 6:56
  • Seems whatever dedupedOptions is has a dependency on the categoryType state. I think much of your logic compuing dedupdedOptions could be moved into a useEffect hook with appropriate dependencies. Commented Jun 15, 2021 at 6:56

1 Answer 1

2

I reproduced your question by some changes in variable declarations in state. be careful to declare variables in state and do the updates by listening the variable changes inside the useEffect.

here is the working code:\

https://codesandbox.io/s/quirky-http-e264i?file=/src/App.js

import "./styles.css";
import { useState, useContext, useCallback, useEffect } from "react";
export default function App() {
  const [categoryType, setCategory] = useState("Canine");
  const [activeTabIndex, setActiveTabIndex] = useState(0);
  const [productsByCategory, setProductsByCategory] = useState([]);
  const [dedupedOptions, setDedupedOptions] = useState([]);
  const [options, setOptions] = useState(dedupedOptions);

  const products = [
    { tags: ["category:Feline"], name: "one" },
    { tags: ["category:Canine"], name: "two" }
  ];

  useEffect(() => {
    const productsByCategory = products
      .filter((product) => {
        const { tags } = product;
        return !!tags.find((tag) => tag.includes(categoryType));
      })
      .map((product) => ({
        ...product,
        category: product.tags
          .find((tag) => tag.includes("category:"))
          .split(":")[1]
      }));
    setProductsByCategory(productsByCategory);
  }, [categoryType]);

  useEffect(() => {
    let tmp_dedupedOptions = [];
    const tagKeysToDisplay = ["category"];
    productsByCategory.forEach((product) => {
      const { tags } = product;
      tags.forEach((tag) => {
        const parts = tag.split(":");
        const key = parts[0];
        const value = parts[1] || null;
        const validTag = tagKeysToDisplay.find(
          (tagKeyToDisplay) => tagKeyToDisplay === key
        );

        if (
          validTag &&
          !tmp_dedupedOptions.find(
            (dedupedOption) => dedupedOption.value === value
          )
        ) {
          tmp_dedupedOptions = [
            ...tmp_dedupedOptions,
            {
              label: value,
              value,
              selected: false
            }
          ];
        }
      });
    });
    setDedupedOptions(tmp_dedupedOptions);
    setOptions(tmp_dedupedOptions);
  }, [productsByCategory]);

  console.log("options: ", options);

  const changeTab = (index, category) => {
    setCategory(category);
    setActiveTabIndex(index);
  };

  const setFilter = useCallback(
    (selectedOption) => {
      const optionIsActive = options.find(
        (option) => option.value === selectedOption.value
      )?.selected;
      let newOptions = [];
      newOptions = [
        ...options.map((option) => {
          if (option.value === selectedOption.value) {
            return {
              ...option,
              selected: !optionIsActive
            };
          }
          return option;
        })
      ];
      setOptions(newOptions);
    },
    [options]
  );
  // }
  return (
    <div>
      <div className="filter-products__tabs">
        <div
          className={`filter-products__tab
          ${activeTabIndex === 0 ? "is-active" : ""}`}
          onClick={changeTab.bind(this, 0, "Canine")}
        >
          <span>DOG</span>
        </div>
        <div
          className={`filter-products__tab
          ${activeTabIndex === 1 ? "is-active" : ""}`}
          onClick={changeTab.bind(this, 1, "Feline")}
        >
          <span>CAT</span>
        </div>
      </div>
    </div>
  );
}
Sign up to request clarification or add additional context in comments.

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.