0

I am working on a filter for an e-commerce project.

It has an active filters section, where the filters that are active are displayed and can be made inactive. This function works, correctly finds the index and removes the right object. I used some of the same code to create the function that toggles a filters active status, but for some reason it just constantly returns -1 even though the console log shows the object in the array, and shows the object being exactly the same. I am so confused.

Here's the console log of the array:

[
    {
        "section": "Section 1",
        "option": "Option 1"
    },
    {
        "section": "Filter Section",
        "option": "Option 1"
    },
    {
        "section": "Section 1",
        "option": "Option 2"
    }
]

Here's the console log of the object i am trying to find the index of:

{
    "section": "Filter Section",
    "option": "Option 1"
}

And then the console is logging -1 for the index, when it should clearly be 1.

Here's the code:

import styles from '../../styles/SASS/filter.module.scss';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPlus, faMinus, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'
import { useState } from 'react';

export default function Filter(params) {
  // hard coded data for dev purposes
  let myFilters = {sections: [
    {name: "Filter Section", options: ["Option 1", "Option 2", "Option 3"]}
    ]
  };
  
  let [activeFilters, setActiveFilters] = useState([{section: "Section 1", option: "Option 1"},
  {section: "Filter Section", option: "Option 1"},
  {section: "Section 1", option: "Option 2"}
]);

  let [filters, setFilters] = useState(myFilters);
  
  let [openSections, setOpenSections] = useState(["Active Filters"]);

  let isActive = (string) => {
    let section = string.split(":")[0];
    let option = string.split(":")[1];
    let filter = {};
    filter.section = section;
    filter.option = option;
    let currentActiveFilters = [...activeFilters];
    let index = currentActiveFilters.findIndex(activefilter => activefilter == filter);
    // console.log(currentActiveFilters);
    // console.log(filter)
    // console.log(`Index is ${index}`)
    if (index !== -1) {
      return true;
    } else {
      return false;
    }
  }

  let removeActiveFilter = (filter) => {
    let currentActiveFilters = [...activeFilters];
    let index = currentActiveFilters.findIndex((activefilter) => {
      activefilter == filter;
    });
    currentActiveFilters.splice(index, 1);
    setActiveFilters(currentActiveFilters);
  }

  let toggleActive = (string) => {
    let section = string.split(":")[0];
    let option = string.split(":")[1];
    let filter = {};
    filter.section = section;
    filter.option = option;
    let currentActiveFilters = [...activeFilters];
    let index = currentActiveFilters.findIndex((activefilter) => {
      // console.log(activefilter);
      // console.log(filter);
      // activefilter == filter;
      activefilter.section == filter.section && activefilter.option == filter.option
    });
    if (index > 0) {
      currentActiveFilters.splice(index, 1);
      setActiveFilters(currentActiveFilters);
    } else {
      currentActiveFilters.push(filter);
      setActiveFilters(currentActiveFilters);
    }
  }

  let isOpen = (section) => {
    let openedSections = [...openSections];
    let index = openedSections.findIndex(openSection => openSection == section);
    if (index !== -1) {
      return true
    } else {
      return false
    }
  }

  let toggleOpen = (section) => {
    let openedSections = [...openSections];
    let index = openSections.findIndex(openSection => openSection == section);
    if (index === -1) {
      openedSections.push(section);
      setOpenSections(openedSections);
    } else {
      openedSections.splice(index, 1);
      setOpenSections(openedSections);
    }
  }

  return (
    <div className={params.showFilter == true ? `${styles.overlay} ${styles.open}` : `${styles.overlay}`}>
      
      <div className={styles.filter}>

        <div className={styles.header}>
          <p className={styles.title}>Filters</p>
        </div>

        <div className={styles.activeFilters}>
        <div className={styles.sectionHeader} onClick={() => toggleOpen('Active Filters')}>
            <p>Active Filters ({activeFilters.length})</p>
            {isOpen('Active Filters') ? <FontAwesomeIcon icon={faMinus} className={styles.icon}/> : <FontAwesomeIcon icon={faPlus} className={styles.icon}/>}
          </div>

          <div className={activeFilters.length > 0 && isOpen('Active Filters') || isOpen('Active Filters') ? `${styles.sectionContent} ${styles.open}`: `${styles.sectionContent}`}>
            
            {activeFilters.length < 1 ? <p className={styles.message}>You haven't applied any filters yet.</p> : <></>}

            <div className={styles.filtersContainer}>
              {activeFilters.map((filter) => {
                return (
                  <div key={`${filter.section}:${filter.option}`} className={styles.activeFilter}>
                    <p>{filter.section}: </p>
                    <p>{filter.option}</p>
                    <FontAwesomeIcon icon={faTimes} className={styles.removeIcon} onClick={() => removeActiveFilter(filter)}/>
                </div>
                )
              })}
            
            </div>
          </div>

        </div>

        {filters.sections.map((filter) => {
          return (
            <div key={filter.name} className={styles.filterSection}>
              <div className={styles.sectionHeader} onClick={() => toggleOpen(filter.name)}>
                <p>{filter.name}</p>
                {isOpen(filter.name) ? <FontAwesomeIcon icon={faMinus} className={styles.icon}/> : <FontAwesomeIcon icon={faPlus} className={styles.icon}/>}
              </div>

              <div className={isOpen(filter.name) ? `${styles.sectionContent} ${styles.open}`: `${styles.sectionContent}`}>

                {filter.options.map((option) => {
                  return (
                    <div key={`option:${filter.name}:${option}`} 
                    className={isActive(`${filter.name}:${option}`) ? `${styles.filterChoice} ${styles.active}` : `${styles.filterChoice}`}
                    onClick={() => toggleActive(`${filter.name}:${option}`)}>

                    

                      <div className={styles.checkbox}>
                        <FontAwesomeIcon icon={faCheck} className={styles.checkmark} />
                      </div>

                      <p className={styles.label}>{option}</p>
                    </div>
                  )
                })}
              </div>
          </div>
          )
        })}

        

        <div className={styles.footer}>
          <button className={styles.resetBtn} onClick={() => setActiveFilters([])}>Reset Filters</button>
          <button className={styles.closeBtn} onClick={() => params.setShowFilter(false)}>Close</button>
        </div>
      </div>
    </div>
  )
};

YungNoey
  • 53
  • 5
  • 3
    You're comparing objects, not object properties. Object equality is determined by reference, not by shape/value, see: [How to determine equality for two JavaScript objects?](https://stackoverflow.com/questions/201183/how-to-determine-equality-for-two-javascript-objects). You'll need to compare all the relevant properties individually to get the result you're expecting. – pilchard Jul 06 '21 at 22:52
  • Objects aren't compared by value, but my reference (memory allocation). Even identical objects in different variables will *not* match, as unlike primitives which are evaluated by value, objects are by reference. Also - instead of splitting substrings, you could easily just use `JSON.parse(obj)` on the json objects. – Joel Hager Jul 06 '21 at 22:56
  • 1
    Quick fix to this will be: `currentActiveFilters.findIndex(activefilter => JSON.stringify(activefilter) === JSON.stringify(filter))`. Check this topic - [Object comparison in JavaScript](https://stackoverflow.com/questions/1068834/object-comparison-in-javascript) – alexnik42 Jul 06 '21 at 22:56
  • Only if they are in the same order, far better to do it explicitly `let index = currentActiveFilters.findIndex(({section, option}) => section === filter.section && option === filter.option);` – pilchard Jul 06 '21 at 22:57
  • @pilchard, I tried that actually...Still didn't seem to work... But thanks. I'll change my approach. Question for you guys though. It works as expected in the 'removeActiveFilter' function. That's why I was like wait this should work though. Why does it work there and now in other functions? – YungNoey Jul 06 '21 at 23:19
  • 1
    Which indicates you're passing a reference that matches by the time you're removing. – pilchard Jul 06 '21 at 23:20
  • Thanks again! I had a false sense of confidence since the remove function worked (I wrote that first) – YungNoey Jul 06 '21 at 23:39

1 Answers1

1

As explained in the comments, you can't compare objects are equal, unless they reference the same variable. There are some approaches to deep object comparison in this question.

However, a common approach is to add a unique identifier to your objects array. Then you can findIndex of the object matching the same identifier.

[
    {
        "id": "filter1",
        "section": "Section 1",
        "option": "Option 1"
    },
    {
        "id": "filter2",
        "section": "Filter Section",
        "option": "Option 1"
    },
    {
        "id": "filter3",
        "section": "Section 1",
        "option": "Option 2"
    }
]
currentActiveFilters.findIndex(activefilter => activefilter.id === filter.id);
j-petty
  • 2,668
  • 1
  • 11
  • 20