Search code examples
reactjsuse-statestate-managementreact-state-managementreact-context

Filter useContext state in a component without changing global state


I have a component that uses useContext to retrieve the global state, which is a list of movies/TV shows. I'm trying to save this global state into local state so I can filter the list by media type (movie or TV show) on a button click, but the filter should be reflected only locally, without dispatching an action to change the global state (I don't want the complete list to be lost).

However, after storing the global state to the local state, the local state is empty. Here is the component's code:

import React, { useContext, useState, useEffect } from "react";

import { WatchListContext } from "../../contexts/WatchListProvider";

import WatchItem from "../WatchItem";

const WatchList = () => {
  const { state } = useContext(WatchListContext);
  const [watchList, setWatchList] = useState(state);
  const [filter, setFilter] = useState("");

  useEffect(() => {
    setWatchList((previousWatchlist) => {
      const filteredWatchList = previousWatchlist.filter(
        (watchItem) => watchItem.media_type === filter
      );

      return filteredWatchList;
    });
  }, [filter]);

  return (
    <div>
      <div>
        <button onClick={() => setFilter("tv")}>TV</button>
        <button onClick={() => setFilter("movie")}>MOVIES</button>
      </div>

      {watchList
        .filter((watchItem) => watchItem.media_type === filter)
        .map(({ id, title, overview, poster_url, release_date, genres }) => (
          <WatchItem
            key={id}
            title={title}
            overview={overview}
            poster_url={poster_url}
            release_date={release_date}
            genres={genres}
          />
        ))}
    </div>
  );
};

export default WatchList;

I can't understand why this isn't working. Any help will be much appreciated!


Solution

  • You can't straightaway ties your global state with your local state like the way you did (const [watchList, setWatchList] = useState(state);):-

    • by doing this it will automatically ties with your default value of your state declared in context which always gonna be an empty array or [] . So to go around this, why don't you track the incoming changes of state with useEffect, like so:-

    • WatchList.js:-

    import React, { useContext, useState, useEffect } from "react";
    
    import { WatchListContext } from "../../contexts/WatchListProvider";
    
    import WatchItem from "../WatchItem";
    
    const WatchList = () => {
      const { state } = useContext(WatchListContext);
      const [watchList, setWatchList] = useState([]);
      const [filter, setFilter] = useState("");
    
      // handle fetching current changes of state received by Context
      useEffect(() => {
        (() => setWatchList(state))()
      }, [state])  
    
      // handle filtering of data
      useEffect(() => {
        setWatchList((previousWatchlist) => {
          const filteredWatchList = previousWatchlist.filter(
            (watchItem) => watchItem.media_type === filter
          );
    
          return filteredWatchList;
        });
      }, [filter]);
    
      return (
        <div>
          <div>
            <button onClick={() => setFilter("tv")}>TV</button>
            <button onClick={() => setFilter("movie")}>MOVIES</button>
          </div>
    
          {watchList
            .filter((watchItem) => watchItem.media_type === filter)
            .map(({ id, title, overview, poster_url, release_date, genres }) => (
              <WatchItem
                key={id}
                title={title}
                overview={overview}
                poster_url={poster_url}
                release_date={release_date}
                genres={genres}
              />
            ))}
        </div>
      );
    };
    
    export default WatchList;