Search code examples
arraysobjectfilteronchangereact-state

Filtering my react state (array of objects)


I have an event handler that is meant to filter a chosen item out of a stateful array. Currently it is filtering everything out of the array instead of the intended selection.

enter image description here

So when I click the remove button of one card both/all cards are removed even though they have different ids...

Lastly, how can I filter the menu cards based on the searchbar input?

This is what my components looks like

top level

import Favorites from "./Favorites";
import { Menu } from "./Menu";
import { useState } from "react";

import "./App.css";

function App() {
  const [favorite, setFavorite] = useState([]);
  const [array, setArray] = useState([
    {
      text: "Check 123",
      name: "Lahmacun",
      image: "src/lahmacun.jpeg",
      id: self.crypto.randomUUID(),
      liked: false,
    },
    {
      text: "Check 456",
      name: "Chicken",
      image: "src/chicken.webp",
      id: self.crypto.randomUUID(),
      liked: false,
    },
    {
      text: "Check 789",
      name: "Veggies",
      image: "src/vegan.jpeg",
      id: self.crypto.randomUUID(),
      liked: false,
    },
    {
      text: "Check 012",
      name: "Pho",
      image: "src/pho.jpeg",
      id: self.crypto.randomUUID(),
      liked: false,
    },
  ]);

  const handleLike = (card) => {
    setFavorite((prev) => [...prev, card]);
  };

  const handleUnlike = (id) => {
    console.log("removed");
    setFavorite(favorite.filter((item) => item.id == id));
  };

  return (
    <div id="app">
      <h1>Recipes To Try</h1>
      <form className="m-2" action="">
        <div className="row">
          <div className="col-3 ">
            <label className="form-label pt-2" id="searchBar">
              Find A Recipe
            </label>
          </div>
          <div className="col-md-7 col-5">
            <input
              //onChange={filterRecipes}
              type="text"
              className="form-control"
              id="searchBar"
              placeholder="Search"
            />
          </div>
          <div className="col-sm-1 col-2">
            <button type="submit" className="btn btn-primary" id="searchBtn">
              Search
            </button>
          </div>
        </div>
      </form>
      <Favorites favorite={favorite} handleUnlike={handleUnlike} />
      <Menu array={array} handleLike={handleLike} />
    </div>
  );
}

export default App;

favorite component

export default function Favorites({ favorite, handleUnlike }) {
  return (
    <>
      <div className="holder">
        <h3>Favorites</h3>
        <div id="favorites">
          {favorite.map((i) => (
            <div
              className="col col-md mb-3 mb-sm-0"
              key={self.crypto.randomUUID()}
            >
              <div className="card">
                <img src={i.image} alt="" className="card-img-top " />
                <div className="card-body">
                  <h5 className="card-title">{i.name}</h5>
                  <p className="card-text">{i.text}</p>
                  <a
                    onClick={() => handleUnlike(i)}
                    href="#"
                    className="btn btn-primary"
                  >
                    Remove
                  </a>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </>
  );
}

Menu component

import { Cards } from "./Cards";

export function Menu({ array, handleLike }) {
  return (
    <div id="menu">
      <div className="row">
        <div className="col">
          <h3 className="">Ready for Testing</h3>
        </div>
      </div>
      <div className="row">
        <Cards array={array} handleLike={handleLike} />
      </div>
    </div>
  );
}

Card Component

export function Cards({ array, handleLike }) {
  return (
    <>
      {array.map((i) => (
        <div className="col col-md mb-3 mb-sm-0" key={i.id}>
          <div className="card">
            <img src={i.image} alt="" className="card-img-top " />
            <div className="card-body">
              <h5 className="card-title">{i.name}</h5>
              <p className="card-text">{i.text}</p>
              <a
                onClick={() => handleLike(i)}
                href="#"
                className="btn btn-primary"
              >
                Add to Favorites
              </a>
            </div>
          </div>
        </div>
      ))}
    </>
  );
}

Thanks a bunch 💪

I expect just one item to be filtered but both were


Solution

  • problem is your i passed in handleUnlike is not the id but the object.

    <a onClick={() => handleUnlike(i)}
                    href="#"
                    className="btn btn-primary"
                  >
    

    change it to this and it will work

      <a onClick={() => handleUnlike(i.id)}
                href="#"
                className="btn btn-primary"
              >
    

    the origin of your problem is inproper naming of your objects if you give everything a meaningful name, you will spot these errors easier.

    instead of

    {favorite.map((i) => (...))}
    

    do

    {favoriteRecipes.map((recipe) => (...))}
    

    for filtering, instead of showing the original list, you create a second list.

    because you are using a button for searching you will need to add a useRef for the textfield. (this will prevent unnecessary rerendering)

    const [filteredRecipes, setFilteredRecipes] = useState(array);
    const searchTextRef = useRef();
    

    then in the onClick handler of the searchButton, you do the filtering.

    const onSearchRecipe = () => setFilteredRecipes(array.filter(recipe => recipe.name.includes(searchTextRef.current.value) !== -1));
    

    change the part of your inputfield and button to the following

    <div className="col-md-7 col-5">
            <input
              type="text"
              ref={searchTextRef}
              className="form-control"
              id="searchBar"
              placeholder="Search"
            />
          </div>
          <div className="col-sm-1 col-2">
            <button 
              onClick={onSearchRecipe} type="button" className="btn btn-primary" id="searchBtn">
              Search
            </button>
          </div>
    

    and then last pass in your filtered list to the menu instead of the array

     <Menu array={filteredRecipes} handleLike={handleLike} />