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>
);
}
CodePudding user response:
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>
CodePudding user response:
You can use useMemo
to prepare the data to display based on some conditions/filters/search/ordering/ anything else. Do not create 2 additional arrays for storing arrays for 'complete' or 'incomplete'. They take some memory...
So few steps to achieve that:
- 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.
const FILTER_COMPLETED = {
All: "ALL",
Complete: "COMPLETE",
Incomplete: "INCOMPLETE"
};
- Add a
useState
variable to hold active filter for this specific area:
const [filterCompleteMode, setFilterCompleteMode] = useState(
FILTER_COMPLETED.All
);
- Add a
useMemo
variable that will prepare the data to display. You can apply some ordering or additinal filtering here.
const todosToDisplay = useMemo(() => {
if (!todo) return [];
switch (filterCompleteMode) {
case FILTER_COMPLETED.All:
return todo;
case FILTER_COMPLETED.Incomplete:
return todo.filter((x) => x.completed === false);
case FILTER_COMPLETED.Complete:
return todo.filter((x) => x.completed === true);
default:
return todo;
}
}, [todo, filterCompleteMode]);
- Modify your JSX a bit
<button
type="button"
onClick={() => setFilterCompleteMode(FILTER_COMPLETED.All)}
>
All
</button>
<button
type="button"
onClick={() => setFilterCompleteMode(FILTER_COMPLETED.Complete)}
>
Completed
</button>
<button
type="button"
onClick={() => setFilterCompleteMode(FILTER_COMPLETED.Incomplete)}
>
Incomplete
</button>
/* ... */
{todosToDisplay.map((items) => ...}
CodePudding user response:
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.
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>
CodePudding user response:
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