Reputation: 274
Im fetching data from an API and changing the value from the frontend to display it in a table. Im fetching a list of objects and storing it in a state and displaying the objects in the state in a html table. The table has a checkbox to display a boolean value. If the value is true, then defaultChecked is true in the checkbox. There's a checkbox in the table header to check or uncheck all items.
The following is the json object which i fetch.
{
completed: false
id: 1
title: "delectus aut autem"
userId: 1
}
If I checked the checkbox in the table header, I want to set completed
to true
in all 200 items.
The following is the code to set all items to either true or false.
const checkAllHandler = (e) => {
let val = e.target.checked;
console.log(val);
let allTodoList = [];
if (val === true) {
if (todo.length > 0) {
for (let index = 0; index < todo.length; index++) {
const newObject = {
userId: todo[index].userId,
id: todo[index].id,
title: todo[index].title,
completed: true,
};
allTodoList.push(newObject);
}
setTodo(allTodoList);
}
} else if (val === false) {
if (todo.length > 0) {
for (let index = 0; index < todo.length; index++) {
const newObject = {
userId: todo[index].userId,
id: todo[index].id,
title: todo[index].title,
completed: false,
};
allTodoList.push(newObject);
}
setTodo(allTodoList);
}
}
};
When I console the todo
state, all the values are updated to either true or false but it doesnt show on the table.
The table has a filter function. If I filter a word and go back, then the values of entire table is changing. I want to display the change when the checkbox is clicked and not when search and go back. How to I do that?
The complete code of the component is below.
const DataTable = () => {
const { loading, items } = useSelector((state) => state.allData);
// console.log(items)
const dispatch = useDispatch();
// const history = useNavigate();
const [searchText, setsearchText] = useState("");
const [todo, setTodo] = useState(items);
console.log("todo");
console.log(todo);
const [isFetch, setisFetch] = useState(false);
const [checkedloading, setcheckedLoading] = useState(false);
const [isChecked, setisChecked] = useState(false);
console.log(isChecked);
useEffect(() => {
const setDataToState = () => {
if (loading === false) {
setTodo(items);
}
};
setDataToState();
}, [loading]);
useEffect(() => {
dispatch(getData());
// setTodo(items)
// console.log(items)
}, [dispatch]);
//etTodo(items)
const handleSearch = (event) => {
//let value = event.target.value.toLowerCase();
setsearchText(event.target.value);
};
const onChangeHandler = (e, item) => {
console.log(e.target.checked);
console.log(item);
if (e.target.checked === true) {
// const item = todo.filter(x=> x.id === id)
// console.log("added")
// console.log(item)
for (let index = 0; index < todo.length; index++) {
if (todo[index].id === item.id) {
console.log(todo[index]);
console.log("deleting");
todo.splice(index, 1);
console.log("deleted");
const newObject = {
userId: item.userId,
id: item.id,
title: item.title,
completed: true,
};
console.log("adding updated object");
todo.splice(index, 0, newObject);
console.log("added");
console.log(todo);
}
}
} else {
// const item = todo.filter(x=> x.id === id)
// console.log("removed")
// console.log(item)
for (let index = 0; index < todo.length; index++) {
if (todo[index].id === item.id) {
console.log(todo[index]);
console.log("deleting");
todo.splice(index, 1);
console.log("deleted");
const newObject = {
userId: item.userId,
id: item.id,
title: item.title,
completed: false,
};
console.log("adding updated object");
todo.splice(index, 0, newObject);
console.log("added");
console.log(todo);
}
}
}
};
const onSubmitHandler = () => {
localStorage.setItem("items", JSON.stringify(todo));
};
const getItem = () => {
const items = localStorage.getItem("items");
console.log("local storage items");
console.log(JSON.parse(items));
};
const checkAllHandler = async (e) => {
const { checked } = e.target;
console.log(checked);
setTodo((todos) =>
todos.map((todo) => ({
...todo,
completed: checked,
}))
);
};
return (
<>
{console.log("todo in render")}
{console.log(todo)}
<div className={styles.container}>
<div className={styles.top}>
<div className={styles.search_bar}>
<input
type="text"
onChange={(e) => handleSearch(e)}
placeholder="search by name"
/>
</div>
</div>
<div className={styles.btn_container}>
<button onClick={onSubmitHandler}>Submit</button>
</div>
<div className={styles.data_table_container}>
{checkedloading === false ? (
<>
<div className={styles.data_table}>
{loading || todo === null || todo === undefined ? (
<>
<p>Loading!!</p>
</>
) : (
<>
<table>
<tr>
<th>ID</th>
<th>userId</th>
<th>Title</th>
<th>
<>
Completed
<input type="checkbox" onChange={checkAllHandler} />
</>
</th>
</tr>
<tbody>
{todo
.filter((val) => {
if (searchText === "") {
return val;
} else if (
val.title.toLowerCase().includes(searchText)
) {
return val;
}
})
.map((item) => (
<>
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.userId}</td>
<td>{item.title}</td>
<td>
<input
type="checkbox"
defaultChecked={item.completed}
onClick={(e) => onChangeHandler(e, item)}
/>
</td>
</tr>
</>
))}
</tbody>
</table>
</>
)}
</div>
</>
) : (
<>Loading</>
)}
</div>
</div>
</>
);
};
CodeSandbox link: https://codesandbox.io/s/flamboyant-proskuriakova-60t19
Upvotes: 1
Views: 829
Reputation: 202686
I don't see any overt issues in this code, but it is quite verbose. Both logic branches are identical save for the completed
boolean value assigned. When updating arrays in React it is common to use functional state updates to make the shallow copy of the previous state, not whatever state may be closed over in the callback scope.
Example:
const checkAllHandler = (e) => {
const { checked } = e.target;
console.log(checked);
setTodo(todos => todos.map(todo => ({
...todo,
completed: checked,
})));
};
If there's still further issue from here with updating the table then please add the rest of the component code and any other code you think is relevant.
The reason none of the checkbox inputs are changing in the table is because you've used the defaultChecked
prop, which makes these inputs fully uncontrolled inputs. They take the initial item.completed
value when mounted and don't change from there other than if you interact with the checkbox.
If you want them to respond to changes/updates to the todo
state then they should be converted to fully controlled inputs and use the checked
prop.
<input
type="checkbox"
checked={item.completed}
onClick={(e) => onChangeHandler(e, item)}
/>
The individual checkbox inputs were being mutated with Array.prototype.splice
in onChangeHandler
. .splice
does an in-place mutation. Again a functional state update should be used to shallow copy the previous state and check for the matched todo
object by id
.
const onChangeHandler = (e, item) => {
const { checked } = e.target;
setTodo((todos) =>
todos.map((todo) =>
todo.id === item.id
? {
...todo,
completed: checked
}
: todo
)
);
};
Upvotes: 1