1

I am making a simple todo. I am fetching data from an API and I want to show all the items in a table by default. There will be 3 buttons - All, Complete and Incomplete which will show All, Completed and Incompleted todos table respectively. I have set states for completed and incompleted todos but can't wrap my head around how to perform conditional rendering and display different tables on different button clicks.

Below is my code -

import React, { useState, useEffect } from "react";
import axios from "axios";
import "./style.css";

export default function App() {

  const URL = 'https://jsonplaceholder.typicode.com/todos';

  const [todo, setTodo] = useState([]);
  const [completed, setCompleted] = useState([]);
  const [incomplete, setIncomplete] = useState([]);

  useEffect(()=>{
    axios.get(URL)
    .then(res=>setTodo(res.data));
  },[])

  const showCompleted = () =>{
    const completeTask = todo.filter((items)=>items.completed===true);
    setCompleted(completeTask);
  }

  const showIncomplete = () =>{
    const incompleteTask = todo.filter((items)=>items.completed===false);
    setIncomplete(incompleteTask);
  }

  return (
    <div>
      <h1>ToDos!</h1>
      <button type="button">All</button>
      <button type="button" onClick={showCompleted}>Completed</button>
      <button type="button" onClick={showIncomplete}>Incomplete</button>
      <hr />
      <table>
        <tr>
          <th>ID</th>
          <th>Title</th>
          <th>Completed</th>
        </tr>
        {todo.map((items)=>
          <tr key={items.id}>
          <td>{items.id}</td>
          <td>{items.title}</td>
          <td><input type="checkbox" defaultChecked={items.completed ? true : false} /></td>
          </tr>
          )}
      </table>
    </div>
  );
} 

4 Answers 4

2
  1. Instead of maintaining a separate state for each type have one type state that the buttons update when they're clicked. Add data attributes to the buttons to indicate what type they are and which can be picked up in the click handler.

  2. Instead of mapping over the whole set of todos, call a function that filters out the set of data from the todo state that you need.

const { useEffect, useState } = React;

const URL = 'https://jsonplaceholder.typicode.com/todos';

function Example() {

  const [todos, setTodos] = useState([]);
  const [type, setType] = useState('all');

  useEffect(()=>{
    fetch(URL)
      .then(res => res.json())
      .then(data => setTodos(data));
  }, []);

  // Filter the todos depending on type
  function filterTodos(type) {
    switch(type) {
      case 'completed': {
        return todos.filter(todo => todo.completed);
      }
      case 'incomplete': {
        return todos.filter(todo => !todo.completed);
      }
      default: return todos;
    }
  }

  // Set the type when the buttons are clicked
  function handleClick(e) {
    const { type } = e.target.dataset;
    setType(type);
  }

  // Call the filter function to get the
  // subset of todos that you need based
  // on the type
  return (
    <div>
      <h1>ToDos!</h1>
      <button
        type="button"
        className={type === 'all' && 'active'}
        data-type="all"
        onClick={handleClick}
      >All
      </button>
      <button
        type="button"
        className={type === 'completed' && 'active'}
        data-type="completed"
        onClick={handleClick}
      >Completed
      </button>
      <button
        type="button"
        className={type === 'incomplete' && 'active'}
        data-type="incomplete"
        onClick={handleClick}
      >Incomplete
      </button>
      <hr />
      <table>
        <tr>
          <th>ID</th>
          <th>Title</th>
          <th>Completed</th>
        </tr>
        {filterTodos(type).map(todo => {
          const { id, title, completed } = todo;
          return (
            <tr key={id}>
              <td>{id}</td>
              <td>{title}</td>
              <td>
                <input
                  type="checkbox"
                  defaultChecked={completed ? true : false}
                />
              </td>
            </tr>
          );
        })}
      </table>
    </div>
  );

}

ReactDOM.render(
  <Example />,
  document.getElementById('react')
);
button { margin-right: 0.25em; }
button:hover { cursor:pointer; }
.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Sign up to request clarification or add additional context in comments.

Comments

1

Keep two states, one to store the initial data and another one to keep track of actually displayed data.

Try like this:

function App() {
  const URL = "https://jsonplaceholder.typicode.com/todos";

  const [todo, setTodo] = React.useState([]);
  const [view, setView] = React.useState([]);

  React.useEffect(() => {
    fetch(URL)
      .then((res) => res.json())
      .then((result) => {
        setTodo(result);
        setView(result);
      });
  }, []);

  const showAll = () => {
    setView(todo);
  };

  const showCompleted = () => {
    const completeTask = todo.filter((items) => items.completed === true);
    setView(completeTask);
  };

  const showIncomplete = () => {
    const incompleteTask = todo.filter((items) => items.completed === false);
    setView(incompleteTask);
  };

  return (
    <div>
      <h1>ToDos!</h1>
      <button type="button" onClick={showAll}>
        All
      </button>
      <button type="button" onClick={showCompleted}>
        Completed
      </button>
      <button type="button" onClick={showIncomplete}>
        Incomplete
      </button>
      <hr />
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Title</th>
            <th>Completed</th>
          </tr>
        </thead>
        <tbody>
          {view.map((items) => (
            <tr key={items.id}>
              <td>{items.id}</td>
              <td>{items.title}</td>
              <td>
                <input
                  type="checkbox"
                  defaultChecked={items.completed ? true : false}
                />
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

1 Comment

Simple and easy to understand. Thanks, it solved my confusion!!
1

You can use useMemo to prepare the data to display based on some conditions/filters/search/ordering/ anything else.

So few steps to achieve that:

  1. Optional, declare some object outside of the component to hold some constants. Maybe I choosed a poor name for that but the idea itself should be ok. FILTER_COMPLETED in the code.

  2. Add a useState variable to hold active filter for this specific area. const [filterCompleteMode, setFilterCompleteMode] = useState(...) in the code.

  3. Add a useMemo variable that will prepare the data to display. You can apply some ordering or additinal filtering here. todosToDisplay in the code.

  4. Modify your JSX a bit, change <button>s and todo to todosToDisplay.

const { useState, useMemo, useEffect } = React;

const FILTER_COMPLETED = {
  All: "ALL",
  Complete: "COMPLETE",
  Incomplete: "INCOMPLETE"
};

function App() {
  const URL = "https://jsonplaceholder.typicode.com/todos";

  const [todos, setTodos] = useState([]);

  const [filterCompleteMode, setFilterCompleteMode] = useState(
    FILTER_COMPLETED.All
  );

  const todosToDisplay = useMemo(() => {
    if (!todos) return [];
    switch (filterCompleteMode) {
      case FILTER_COMPLETED.All:
        return todos;
      case FILTER_COMPLETED.Incomplete:
        return todos.filter((x) => x.completed === false);
      case FILTER_COMPLETED.Complete:
        return todos.filter((x) => x.completed === true);
      default:
        return todos;
    }
  }, [todos, filterCompleteMode]);

  useEffect(() => {
    fetch(URL)
      .then((res) => res.json())
      .then((data) => setTodos(data));
  }, []);

  const onCompleteFilterClick = (e) => {
    setFilterCompleteMode(e.target.dataset.mode);
  };

  return (
    <div>
      <h1>ToDos!</h1>
      <button
        type="button"
        data-mode={FILTER_COMPLETED.All}
        onClick={onCompleteFilterClick}
      >
        All
      </button>
      <button
        type="button"
        data-mode={FILTER_COMPLETED.Complete}
        onClick={onCompleteFilterClick}
      >
        Completed
      </button>
      <button
        type="button"
        data-mode={FILTER_COMPLETED.Incomplete}
        onClick={onCompleteFilterClick}
      >
        Incomplete
      </button>
      <hr />
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Title</th>
            <th>Completed</th>
          </tr>
        </thead>
        <tbody>
          {todosToDisplay.map((item) => (
            <tr key={item.id}>
              <td>{item.id}</td>
              <td>{item.title}</td>
              <td>
                <input
                  type="checkbox"
                  defaultChecked={item.completed ? true : false}
                />
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>

Comments

0

Create state:

 const [whatShow, setWhatShow] = useState('All').

When you click on button change this state next:

  {todo.map((items)=>
    {items.completed === whatShow && <tr key={items.id}>
      <td>{items.id}</td>
      <td>{items.title}</td>
      <td><input type="checkbox" defaultChecked={items.completed ? true : false} /></td>
    </tr>}

)}

something like this

2 Comments

You are comparing a string(whatShow) with a boolean (items.completed). It will not work.
Of course, it was fast example. You can: use 2 useState: 1 filter (true/false), 2 (filter on\off). Or useState = {filterOn: true, whatShow: false}. Or use my first example and just change useState string on boolean, because you will be use ===, 'null' will not be equal 'false'. but need change === on !== and whatShow on !whatShow

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.