skip to Main Content

I have 3 buttons to filter data which fetched from API. I have filter and log to console successfully but I can’t display on screen when I clicked on. I want it show "All" as default

Here is my code.

Data.jsx file

export default function Data(props) {
  return (
    <div className="data--item" key={props.id}>
      <p>userId: {props.userId}</p>
      <p>title: {props.title}</p>
      <p>completed: {props.completed}</p>
    </div>
  );
}

App.jsx file:

import React from "react";
import Data from "../components/Data";
import "./App.css";

export default function App() {
  const [toDoData, setToDoData] = React.useState([]);
  const [filterCompleted, setFilterCompleted] = React.useState(toDoData);
  
React.useEffect(function () {
    fetch(`https://jsonplaceholder.typicode.com/todos`)
      .then((res) => res.json())
      .then((data) => {
        setToDoData(data);
      });
  }, []);


  const data = toDoData.map((item) => { 
    return (
      <Data
        key={item.id}
        userId={item.userId}
        title={item.title}
        completed={item.completed ? "true" : "false"}
      />
    );
  });

  function handleCompleted() {
    const completedArray = toDoData.filter((data) => data.completed);
    setFilterCompleted(completedArray);
    console.log(completedArray);
  }

  function handleNotCompleted() {
    const notCompletedArray = toDoData.filter((data) => !data.completed);
    setFilterCompleted(notCompletedArray);
    console.log(notCompletedArray);
  }

  function handleAll() {
    setFilterCompleted(toDoData);
    console.log(toDoData);
  }

  return (
    <div>
      <div className="btn">
        <button onClick={() => handleCompleted()}>Completed</button>
        <button onClick={() => handleNotCompleted()}>Not Completed</button>
        <button onClick={() => handleAll()}>All</button>
      </div>
      {filterCompleted && <div className="data">{data}</div>}
    </div>
  );
}

If I change toDoData.map to filterCompleted.map, it displays distinct successfully but it doesn’t display anything at first (I want display "all" as default when it render)

3

Answers


  1. You can store data in one value, which is not shown, and set one more to show.

    const [originalToDos, setOriginalTodos] = useState([]);
    const [todos, setTodos] = useState([]);
    const [filter, setFilter] = useState("all");//default
    useEffect(() => {
      // check for empty array
      if(originalTodos.length === 0) {
        return;
      }
      if(filter === "all"){
        setTodos(originalTodos);
      } else if(filter === "completed") {
        setTodos(originalTodos.filter(td => td.completed === true))
      }  else {
        setTodos(originalTodos.filter(td => td.completed !== true))
      }
    }, [filter])
    
    const changeFilter = (f) => {
      setFilter(f)
    }
    

    And change the event handlers of buttons

    <button onClick={() => {
       changeFilter("completed");
     }}>Completed</button>
    ...
    
    Login or Signup to reply.
  2. There is an overriding problem here in that you have these two bits of state: toDoData which seems to be the canonical source and filterCompleted which contains the subset.

    You could rewire your code to work with your existing state items, but the problems you are having are typical of ones caused by unnecessary complexity related to duplicated states. So it’s best to remodel how this works slightly.

    The second state item filterCompleted shouldn’t exist. It is a derivation of toDoData. It’s generally best to avoid having new state items that duplicate data from another because that means you now have to manage two sets of state that are directly related, which is an overhead that usually is not necessary. It opens you up to a much greater risk for a whole category of code smells and pitfalls but doesn’t gain you anything in this scenario. You want to minimize duplication.

    This case is quite typical of this. It could be made to work, but the entire problem and complexity that has caused the confusion doesn’t even need to exist.

    Instead, store only what filter is active, and of course the original data from the API itself. This is the absolute minimal set of state that’s needed to achieve your use case, which should usually be the goal when thinking about what state you need. It implies you only have state that is fundamental to the problem, and so therefore you can be confident any complexity is necessary rather than a portion of it being unnecessary.

    Of course, deep inside the browser the filtered state still exists, duplicated in memory. But React wants you to represent the problem in the most declarative & succint way possible, and doing that reduces complexity in your code, and so also real and potential bugs.

    We use new string identifiers for each available filter and set the default to all. We then compute the filtered data via useMemo by taking into account the new activeFilter state and the original toDoData. This will recompute only when either toDoData changes (i.e. when it first loads from the API) or activeFilter changes. When that happens it gets recomputed right in the same render cycle. It will also compute on the first render. Since the default filter is all on the first render filteredData will reflect this and have all of the data.

    The button click handlers now simply change the string identifier for the active filter to the one relevant for each option.

    As well as being easier to understand, this approach wipes out likely problems in the future. Imagine if in future you wanted a refresh button to get the latest data from the API. In the old solution, you’d have to carefully make sure both the toDoData and filterCompleted states were reset such that they remain synced. In the new solution, you’d simply update toDoData and everything else would take care of itself. The scope for human error is reduced.

    import React from "react";
    import Data from "../components/Data";
    import "./App.css";
    
    export default function App() {
      const [toDoData, setToDoData] = React.useState([]);
      const [activeFilter, setActiveFilter] = React.useState("all");
    
      React.useEffect(function () {
        fetch(`https://jsonplaceholder.typicode.com/todos`)
          .then((res) => res.json())
          .then((data) => {
            setToDoData(data);
          });
      }, []);
    
      const filteredData = React.useMemo(() => {
        if (activeFilter === "all") return toDoData;
    
        if (activeFilter === "completed")
          return toDoData.filter((data) => data.completed);
    
        if (activeFilter === "not-completed")
          return toDoData.filter((data) => !data.completed);
    
        throw new Error(`Unknown filter '${activeFilter}'`);
      }, [toDoData, activeFilter]);
    
      const renderedData = filteredData.map((item) => {
        return (
          <Data
            key={item.id}
            userId={item.userId}
            title={item.title}
            completed={item.completed ? "true" : "false"}
          />
        );
      });
    
      return (
        <div>
          <div className="btn">
            <button onClick={() => setActiveFilter("completed")}>Completed</button>
            <button onClick={() => setActiveFilter("not-completed")}>
              Not Completed
            </button>
            <button onClick={() => setActiveFilter("all")}>All</button>
          </div>
          <div className="data">{renderedData}</div>
        </div>
      );
    }
    
    
    Login or Signup to reply.
  3. set states and useEffect like this const [toDoData, setToDoData] =seState([]); const [filterCompleted, setFilterCompleted] = useState([]);

    useEffect(() => {
    fetch(https://jsonplaceholder.typicode.com/todos)
    .then((res) => res.json())
    .then((data) => {
    setToDoData(data);
    setFilterCompleted(data)
    });
    }, []);
    render data like this
    {filterCompleted.length > 0 && {data}}

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search