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
You can store data in one value, which is not shown, and set one more to show.
And change the event handlers of buttons
There is an overriding problem here in that you have these two bits of state:
toDoData
which seems to be the canonical source andfilterCompleted
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 oftoDoData
. 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 viauseMemo
by taking into account the newactiveFilter
state and the originaltoDoData
. This will recompute only when eithertoDoData
changes (i.e. when it first loads from the API) oractiveFilter
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 isall
on the first renderfilteredData
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
andfilterCompleted
states were reset such that they remain synced. In the new solution, you’d simply updatetoDoData
and everything else would take care of itself. The scope for human error is reduced.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}}