Negin
Negin

Reputation: 331

multiple use state in react hooks

I'm reading the answer of this question: Problem with conditional statements in React Hook how can I Rewrite this code with multiple use state? in this answer gather all the state objects to a single state.is there any way to rewrite this code with multiple and separate state?

const initialState = {
  movies: [],
  loading: true,
  searchKeyword: "",
  filterOverSeven: false,
  filterByWatch: "ALL" // Can be "ALL" | "WATCHED" | "NOT_WATCHED"
};

const App = () => {
  const [state, setState] = useState(initialState);

  useEffect(() => {
    fetch("https://my-json-server.typicode.com/bemaxima/fake-api/movies")
      .then((response) => response.json())
      .then((response) => {
        setState((s) => ({
          ...s,
          movies: response.map((item) => ({
            id: item.id,
            name: item.name,
            rate: item.rate,
            watched: false
          })),
          loading: false
        }));
      });
  }, []);

  function handleWatchedBtn(id) {
    setState((s) => ({
      ...s,
      movies: s.movies.map((movie) => {
        if (movie.id === id) {
          return { ...movie, watched: !movie.watched };
        }
        return movie;
      })
    }));
  }

  function handleKeywordChange(e) {
    setState((s) => ({ ...s, searchKeyword: e.target.value }));
  }

  function handleOverSevenChange(e) {
    setState((s) => ({ ...s, filterOverSeven: !s.filterOverSeven }));
  }

  function handleWatchedChange(filter) {
    setState((s) => ({ ...s, filterByWatch: filter }));
  }

  function filterItems() {
    return state.movies
      .filter((item) =>
        item.name.toLowerCase().includes(state.searchKeyword.toLowerCase())
      )
      .filter((item) => (state.filterOverSeven ? item.rate > 7 : true))
      .filter((item) =>
        state.filterByWatch === "ALL"
          ? true
          : item.watched === (state.filterByWatch === "WATCHED")
      );
  }

  if (state.loading) {
    return "Please wait...";
  }

  return (
    <div>
      <div>
        <div>
          Keyword
          <input
            type="text"
            value={state.searchKeyword}
            onChange={handleKeywordChange}
          />
        </div>
        <div>
          <button onClick={() => handleWatchedChange("ALL")}>all</button>
          <button onClick={() => handleWatchedChange("WATCHED")}>watch</button>
          <button onClick={() => handleWatchedChange("NOT_WATCHED")}>
            not watch
          </button>
        </div>
        <div>
          Only over 7.0
          <input
            type="checkbox"
            checked={state.filterOverSeven}
            onChange={handleOverSevenChange}
          />
        </div>
        <div>
          <ul>
            {filterItems().map((movie) => (
              <li data-id={movie.id}>
                {`${movie.id} : ${movie.name} ${movie.rate}`}{" "}
                <button onClick={() => handleWatchedBtn(movie.id)}>
                  {movie.watched ? "Watched" : " Not watched"}
                </button>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
};

export default App;

this is my code and how I try to change that.

import React, { useEffect, useState } from "react";
import { getAllMovies } from "./components/Transportlayer";

const App = () => {
  const [movies, setMovies] = useState([]);
  const [Keyword, setKeyword] = useState("");
  const [OverSeven, setOverSeven] = useState(false);
  const [Loading, setLoading] = useState(true);
  const [filterByWatch, setfilterByWatch] = useState("ALL");

  useEffect(() => {
    getAllMovies().then((response) => {
      setMovies(
        response.map((item) => ({
          id: item.id,
          name: item.name,
          rate: item.rate,
          watched: false,
        }))
      );
      setLoading(false);
    });
  }, []);

  function handleWatchedBtn(id) {
    setMovies({
      movies: movies.map((movie) => {
        if (movie.id === id) {
          return { movie, watched: !movie.watched };
        }
        return movie;
      })
    });
  }


  function handleKeywordChange(e) {
    setKeyword(e.target.value);
  }

  function handleOverSevenChange(e) {
    setOverSeven(e.target.checked);
  }

  function handleWatchedChange(filter) {
    setfilterByWatch({ filterByWatch: filter });
  }

  function filterItems() {
    return movies
      .filter((item) =>
        item.name.toLowerCase().includes(Keyword.toLowerCase())
      )
      .filter((item) => (OverSeven ? item.rate > 7 : true))
      .filter((item) =>
         filterByWatch === "ALL"
          ? true
          : item.watched === (filterByWatch === "WATCHED")
      );
  }

  if (Loading) {
    return "Please wait...";
  }

  return (
    <div>
      <div>
        <div>
          Keyword
          <input
            type="text"
            value={Keyword}
            onChange={handleKeywordChange}
          />
        </div>
        <div>
          <button onClick={() => handleWatchedChange("ALL")}>all</button>
          <button onClick={() => handleWatchedChange("WATCHED")}>done</button>
          <button onClick={() => handleWatchedChange("NOT_WATCHED")}>
            undone
          </button>
        </div>
        <div>
          Only over 7.0
          <input
            type="checkbox"
            checked={OverSeven}
            onChange={handleOverSevenChange}
          />
        </div>
        <div>
          <ul>
            {filterItems().map((movie) => (
              <li data-id={movie.id}>
                {`${movie.name} ${movie.rate}`}
                <button onClick={() => handleWatchedBtn(movie.id)}>
                  {movie.watched ? "done" : " undone"}
                </button>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
};

export default App;

Upvotes: 1

Views: 1087

Answers (4)

Rabi jha
Rabi jha

Reputation: 328

You can use useReducer hook to better refactor this code.

Upvotes: 0

Negin
Negin

Reputation: 331

This is the answer given by @Luca Pizzini and work for this problem.

change button text and state by click and show list in react hooks

Upvotes: 0

Eduardo Wronscki
Eduardo Wronscki

Reputation: 305

This functions is wrong

 function handleWatchedBtn(id) {
    setMovies({
      movies: movies.map((movie) => {
        if (movie.id === id) {
          return { movie, watched: !movie.watched };
        }
        return movie;
      })
    });
  }

you are returning an object with key movies inside movies state, but movies should be an array of movies. Try something like this:

  function handleWatchedBtn(id) {
    setMovies(() => movies.map((movie) => {
        if (movie.id === id) {
          return { movie, watched: !movie.watched };
        }
        return movie;
      )
    });
  }

Upvotes: 0

Erfan
Erfan

Reputation: 1802

In the second code (separated states), you define movies as an array, but you are changing it to an object:

     function handleWatchedBtn(id) {
    setMovies({
      movies: movies.map((movie) => {
        if (movie.id === id) {
          return { movie, watched: !movie.watched };
        }
        return movie;
      })
    });
  }

I think this would work:

   function handleWatchedBtn(id) {
    setMovies(
       movies.map((movie) => {
        if (movie.id === id) {
          return { movie, watched: !movie.watched };
        }
        return movie;
      })
    );
  }

Same with the filterByWatch state, its defined to be a string but in here you are setting to it to a object:

     function handleWatchedChange(filter) {
    setfilterByWatch({ filterByWatch: filter });
  }

correct:

     function handleWatchedChange(value) {
    setfilterByWatch(value);
  }

Upvotes: 1

Related Questions