I don't understand why below code doesn't change state. Even when 'if' statement is executed, state is the same. Why case with if doesn't change state?
class Welcome extends React.Component {
state = {
items: [
{
id: 1,
done: false,
},
{
id: 2,
done: false,
},
{
id: 3,
done: false,
},
]
}
handleDone = (index) => {
this.setState((prevState) => {
const copyItems = [...prevState.items];
if (copyItems[index].done === false) {
console.log("Done should be true");
copyItems[index].done = true;
} else {
console.log("Done should be false");
copyItems[index].done = false;
}
// copyItems[index].done = !copyItems[index].done - the same result
return {
items: [...copyItems],
};
});
}
render() {
return (
this.state.items.map((item, index) => {
return (
<div>
<span>id: {item.id}</span>
<span> {item.done ? "- is not done" : "- is done"} </span>
<button
onClick={() => this.handleDone(index)}>
Change to opposite
</button>
</div>
)
})
)
}
}
ReactDOM.render(<Welcome />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
this.setState((prevState) => {
const copyItems = [...prevState.items];
if (copyItems[index].done === false) {
console.log("Done should be true");
copyItems[index].done = true;
} else {
console.log("Done should be false");
copyItems[index].done = false;
}
// copyItems[index].done = !copyItems[index].done - the same result
return {
items: [...copyItems],
};
});
Below example works fine when there is no if statement:
this.setState((prevState) => {
const copyItems = [...prevState.items];
copyItems[index].done = true;
return {
items: [...copyItems],
};
});
Below example works fine with 'if' statement in case when object is copied:
this.setState((prevState) => {
const copyItems = JSON.parse(JSON.stringify([...prevState.items]));
copyItems[index].done = !copyItems[index].done
return {
items: [...copyItems],
};
});
What's wrong for React is that even if you spread your array, the object inside it are still referenced as the object inside your state. So by doing copyItems[index].done = true; you're actually mutating the state directly (this.state[index].done should be true also). What you can do to avoid this is to use .map on your prevState so you're not updating the state directly.
const state = [{ done: true }];
const stateCopy = [...state];
const toFind = 0;
stateCopy[toFind].done = false;
console.log(stateCopy[toFind], state[toFind]); // { done: false } , { done: false } /!\ state should not be updated at this point.
// good way of doing that might be by using prevState.items.map() and do your logic inside it
const stateUpdated = state.map((el, index) => {
if (index === toFind) {
return { done: !el.done }
}
return el;
});
console.log(stateUpdated[toFind], state[toFind]) // state is not mutated, as expected
Problem was caused by create-react-app that wrap app with <React.StrictMode>.
To resolve it just remove this wrapping tag. And that's it.
It's expected behaviour and it should help avoid bugs on prod. More details in React docs.
Related
Making a employee directory and I'm trying to convert class component into functional component. The nav, search, and table will render but the call to the api isn't working. Getting a
TypeError: users.filter is not a function Here is the class component that works and the in progress functional component. I can't figure out what is different.
import React, { Component } from "react";
import DataTable from "./DataTable";
import Nav from "./Nav";
import API from "../utils/API";
import "../styles/DataArea.css";
export default class DataArea extends Component {
state = {
users: [{}],
order: "descend",
filteredUsers: [{}]
}
headings = [
{ name: "Image", width: "10%" },
{ name: "Name", width: "10%" },
{ name: "Phone", width: "20%" },
{ name: "Email", width: "20%" },
{ name: "DOB", width: "10%" }
]
handleSort = heading => {
if (this.state.order === "descend") {
this.setState({
order: "ascend"
})
} else {
this.setState({
order: "descend"
})
}
const compareFnc = (a, b) => {
if (this.state.order === "ascend") {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return a[heading].first.localeCompare(b[heading].first);
} else {
return a[heading] - b[heading];
}
} else {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return b[heading].first.localeCompare(a[heading].first);
} else {
return b[heading] - a[heading];
}
}
}
const sortedUsers = this.state.filteredUsers.sort(compareFnc);
this.setState({ filteredUsers: sortedUsers });
}
handleSearchChange = event => {
console.log(event.target.value);
const filter = event.target.value;
const filteredList = this.state.users.filter(item => {
// merge data together, then see if user input is anywhere inside
let values = Object.values(item)
.join("")
.toLowerCase();
return values.indexOf(filter.toLowerCase()) !== -1;
});
this.setState({ filteredUsers: filteredList });
}
componentDidMount() {
API.getUsers().then(results => {
this.setState({
users: results.data.results,
filteredUsers: results.data.results
});
});
}
render() {
return (
<>
<Nav handleSearchChange={this.handleSearchChange} />
<div className="data-area">
<DataTable
headings={this.headings}
users={this.state.filteredUsers}
handleSort={this.handleSort}
/>
</div>
</>
);
}
}
import React, { useState, useEffect } from "react";
import DataTable from "./DataTable";
import Nav from "./Nav";
import API from "../utils/API";
import "../styles/DataArea.css";
const DataArea = () => {
const [users, setUsers] = useState([{}]);
const [order, setOrder] = useState("descend");
const [filteredUsers, setFilteredUsers] = useState([{}]);
const headings = [
{ name: "Image", width: "10%" },
{ name: "Name", width: "10%" },
{ name: "Phone", width: "20%" },
{ name: "Email", width: "20%" },
{ name: "DOB", width: "10%" },
];
const handleSort = (heading) => {
if (order === "descend") {
setOrder((order = "ascend"));
} else {
setOrder((order = "descend"));
}
const compareFnc = (a, b) => {
if (order === "ascend") {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return a[heading].first.localeCompare(b[heading].first);
} else {
return a[heading] - b[heading];
}
} else {
// account for missing values
if (a[heading] === undefined) {
return 1;
} else if (b[heading] === undefined) {
return -1;
}
// numerically
else if (heading === "name") {
return b[heading].first.localeCompare(a[heading].first);
} else {
return b[heading] - a[heading];
}
}
};
const sortedUsers = filteredUsers.sort(compareFnc);
setFilteredUsers({ filteredUsers: sortedUsers });
};
let handleSearchChange = (event) => {
console.log(event.target.value);
const filtered = event.target.value;
const filteredList = users.filter((item) => {
// merge data together, then see if user input is anywhere inside
let values = Object.values(item).join("").toLowerCase();
return values.indexOf(filtered.toLowerCase()) !== -1;
});
setFilteredUsers((filteredUsers = filteredList));
};
useEffect(() => {
API.getUsers().then((results) => {
setUsers({
users: results.data.results,
filteredUsers: results.data.results,
});
});
}, []);
return (
<>
<Nav handleSearchChange={handleSearchChange} />
<div className="data-area">
<DataTable
headings={headings}
users={filteredUsers}
handleSort={handleSort}
/>
</div>
</>
);
};
export default DataArea;
The issue is in your useEffect hook where you shoehorn both users and filteredUsers into your users state.
useEffect(() => {
API.getUsers().then((results) => {
setUsers({
users: results.data.results,
filteredUsers: results.data.results,
});
});
}, []);
It should be
useEffect(() => {
API.getUsers().then((results) => {
setUsers(results.data.results);
setFilteredUsers(results.data.results),
});
}, []);
You want to populate both the users state, and separately the filteredUsers state.
An issue with setting the sorting order, you are mutating the order state:
if (order === "descend") {
setOrder((order = "ascend")); // <-- mutates
} else {
setOrder((order = "descend")); // <-- mutates
}
You can simply toggle between the two:
setOrder(order => order === 'ascend' ? 'descend' : 'ascend');
Another issue is at the end of your handleSort function:
const sortedUsers = filteredUsers.sort(compareFnc);
setFilteredUsers({ filteredUsers: sortedUsers });
This will nest the filteredUsers array, and also mutates the array in-place, it should probably use a functional state update to update from the previous state, use array.slice to make a copy, and then call sort.
setFilteredUsers(filteredUsers => filteredUsers.slice().sort(compareFnc));
I know this is an over asked question regarding useEffect and useState infinite loops. I have read almost every question about it and also searched over the internet in order to try to fix it before posting here. The most recent article that I read was this one.
Before I read the article my useState function inside useEffect was without the useState's callback, so it was what I thought which was causing the problem. But after moving it from this:
setNotifications({
...notifications,
[type]: {
...notifications[type],
active: portfolioSettings[type],
payload: {
...notifications[type].payload,
[pKey]: portfolioSettings[pKey],
},
},
});
to this:
setNotifications((currState) => ({
...currState,
[type]: {
...currState[type],
active: portfolioSettings[type],
payload: {
...currState[type].payload,
[pKey]: portfolioSettings[pKey],
},
},
}));
Unfortunately the infinite loop persists. Here are all useEffect functions (the last one is the one causing the infinite looping) that I'm using in this component.
// Set default selected portfolio to the active portfolio
useEffect(() => {
let mounted = true;
const setPortfolioData = () => {
if (activePortfolio) {
const portfolioId = activePortfolio.id;
const portfolioName = activePortfolio.name;
setSelectedPortfolio({
portfolioId,
portfolioName,
});
}
};
if (mounted) setPortfolioData();
return () => {
mounted = false;
};
}, [activePortfolio]);
// Set all the categories if no category is set
useEffect(() => {
let mounted = true;
if (mounted && allCvmCategories) {
const allCvmCategoriesNames = allCvmCategories.map((c) => c);
setNotifications((currState) => ({
...currState,
isCvmNotificationEnabled: {
...currState.isCvmNotificationEnabled,
payload: {
...currState.isCvmNotificationEnabled.payload,
cvmCategories: allCvmCategoriesNames,
},
},
}));
setIsCvmCategoriesReady(true);
}
return () => {
mounted = false;
};
}, [allCvmCategories]);
// THE ONE WHICH IS CAUSING INFINITE LOOPING
// THE notificationsTypes AND notificationsInitialState
// ARE DECLARED IN THE ROOT OF THE JSX FILE
// OUT OF THE FUNCTIONAL COMPONENT
useEffect(() => {
let mounted = true;
if (
mounted &&
isCvmCategoriesReady &&
allPortfolios &&
selectedPortfolio.portfolioId
) {
const { portfolioId } = selectedPortfolio;
const portfolioSettings = allPortfolios[portfolioId].settings;
if (portfolioSettings) {
notificationsTypes.forEach((type) => {
if (Object.prototype.hasOwnProperty.call(portfolioSettings, type)) {
const { payloadKeys } = notificationsInitialState[type];
if (payloadKeys.length > 0) {
payloadKeys.forEach((pKey) => {
if (
Object.prototype.hasOwnProperty.call(portfolioSettings, pKey)
) {
setNotifications((currState) => ({
...currState,
[type]: {
...currState[type],
active: portfolioSettings[type],
payload: {
...currState[type].payload,
[pKey]: portfolioSettings[pKey],
},
},
}));
}
});
} else {
setNotifications((currState) => ({
...currState,
[type]: {
...currState[type],
active: portfolioSettings[type],
},
}));
}
}
});
}
}
return () => {
mounted = false;
};
}, [allPortfolios, isCvmCategoriesReady, selectedPortfolio]);
Thanks for the responses. I have found the problem, it was with the hook that I was using (SWR) for fetching. Making the allPortfolios change all the time. I have fixed it wrapping into a new custom hook.
I am trying to conditionally render a component based on toggling of flag inside state. It looks like the state is getting updated but the component is not getting rendered. Can some one tell what is wring here. renderTree function updates the state, but render is not called then.
import React from "react";
import CheckboxTree from "react-checkbox-tree";
import "react-checkbox-tree/lib/react-checkbox-tree.css";
import { build } from "../data";
import { Input, Dropdown } from "semantic-ui-react";
import _ from "lodash";
class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
nodes: build(),
checked: [],
expanded: [],
isDropdownExpanded: false,
keyword: ""
};
}
onCheck = checked => {
this.setState({ checked }, () => {
console.log(this.state.checked);
});
};
onExpand = expanded => {
this.setState({ expanded }, () => {
console.log(this.state.expanded);
});
};
renderTree = () => {
this.setState(
prevState => {
return {
...prevState,
isDropdownExpanded: !prevState.isDropdownExpanded
};
},
() => {
console.log(this.state);
}
);
};
onSearchInputChange = (event, data, searchedNodes) => {
this.setState(prevState => {
if (prevState.keyword.trim() && !data.value.trim()) {
return {
expanded: [],
keyword: data.value
};
}
return {
expanded: this.getAllValuesFromNodes(searchedNodes, true),
keyword: data.value
};
});
};
shouldComponentUpdate(nextProps, nextState) {
if (this.state.keyword !== nextState.keyword) {
return true;
}
if (!_.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
return true;
}
getAllValuesFromNodes = (nodes, firstLevel) => {
if (firstLevel) {
const values = [];
for (let n of nodes) {
values.push(n.value);
if (n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
} else {
const values = [];
for (let n of nodes) {
values.push(n.value);
if (n.children) {
values.push(...this.getAllValuesFromNodes(n.children, false));
}
}
return values;
}
};
keywordFilter = (nodes, keyword) => {
let newNodes = [];
for (let n of nodes) {
if (n.children) {
const nextNodes = this.keywordFilter(n.children, keyword);
if (nextNodes.length > 0) {
n.children = nextNodes;
} else if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.children = nextNodes.length > 0 ? nextNodes : [];
}
if (
nextNodes.length > 0 ||
n.label.toLowerCase().includes(keyword.toLowerCase())
) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
} else {
if (n.label.toLowerCase().includes(keyword.toLowerCase())) {
n.label = this.getHighlightText(n.label, keyword);
newNodes.push(n);
}
}
}
return newNodes;
};
getHighlightText = (text, keyword) => {
const startIndex = text.indexOf(keyword);
return startIndex !== -1 ? (
<span>
{text.substring(0, startIndex)}
<span style={{ color: "red" }}>
{text.substring(startIndex, startIndex + keyword.length)}
</span>
{text.substring(startIndex + keyword.length)}
</span>
) : (
<span>{text}</span>
);
};
render() {
const { checked, expanded, nodes, isDropdownExpanded } = this.state;
let searchedNodes = this.state.keyword.trim()
? this.keywordFilter(_.cloneDeep(nodes), this.state.keyword)
: nodes;
return (
<div>
<Dropdown fluid selection options={[]} onClick={this.renderTree} />
{isDropdownExpanded && (
<div>
<Input
style={{ marginBottom: "20px" }}
fluid
icon="search"
placeholder="Search"
iconPosition="left"
onChange={(event, data) => {
this.onSearchInputChange(event, data, searchedNodes);
}}
/>
<CheckboxTree
nodes={searchedNodes}
checked={checked}
expanded={expanded}
onCheck={this.onCheck}
onExpand={this.onExpand}
showNodeIcon={true}
/>
</div>
)}
</div>
);
}
}
export default Widget;
Problem is in your shouldComponentUpdate method:
shouldComponentUpdate(nextProps, nextState) {
if (this.state.keyword !== nextState.keyword) {
return true;
}
if (!_.isEqual(this.state.checked, nextState.checked)) {
return true;
}
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
return true;
}
Since renderTree only changes isDropdownExpanded value, shouldComponentUpdate always returns false
If shouldComponenetUpdate returns true then your component re-renders, otherwise it dosen't.
In your code sandbox, it can be seen that every time you click on the dropdown, the shouldComponenetUpdate returns false for this condition
if (_.isEqual(this.state.expanded, nextState.expanded)) {
return false;
}
Either you need to change the state of this variable in your renderTree function or you need to re-write this condition as
if (_.isEqual(this.state.isDropdownExpanded, nextState.isDropdownExpanded)) {
return false;
}
Ciao, to force a re-render in React you have to use shouldComponentUpdate(nextProps, nextState) function. Something like:
shouldComponentUpdate(nextProps, nextState) {
return this.state.isDropdownExpanded !== nextState.isDropdownExpanded;
}
When you change isDropdownExpanded value, shouldComponentUpdate will be triggered and in case return is equal to true, component will be re-rendered. Here working example (based on your codesandbox).
I'm having some trouble with the React useState hook. I have a todolist with a checkbox button and I want to update the 'done' property to 'true' that has the same id as the id of the 'clicked' checkbox button. If I console.log my 'toggleDone' function it returns the right id. But I have no idea how I can update the right property.
The current state:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: false
},
]
})
const toggleDone = (id) => {
console.log(id);
}
return (
<div className="App">
<Todos todos={state.todos} toggleDone={toggleDone}/>
</div>
);
}
The updated state I want:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: true // if I checked this checkbox.
},
]
})
You can safely use javascript's array map functionality since that will not modify existing state, which react does not like, and it returns a new array. The process is to loop over the state's array and find the correct id. Update the done boolean. Then set state with the updated list.
const toggleDone = (id) => {
console.log(id);
// loop over the todos list and find the provided id.
let updatedList = state.todos.map(item =>
{
if (item.id == id){
return {...item, done: !item.done}; //gets everything that was already in item, and updates "done"
}
return item; // else return unmodified item
});
setState({todos: updatedList}); // set state to new object with updated list
}
Edit: updated the code to toggle item.done instead of setting it to true.
You need to use the spread operator like so:
const toggleDone = (id) => {
let newState = [...state];
newState[index].done = true;
setState(newState])
}
D. Smith's answer is great, but could be refactored to be made more declarative like so..
const toggleDone = (id) => {
console.log(id);
setState(state => {
// loop over the todos list and find the provided id.
return state.todos.map(item => {
//gets everything that was already in item, and updates "done"
//else returns unmodified item
return item.id === id ? {...item, done: !item.done} : item
})
}); // set state to new object with updated list
}
const toggleDone = (id) => {
console.log(id);
// copy old state
const newState = {...state, todos: [...state.todos]};
// change value
const matchingIndex = newState.todos.findIndex((item) => item.id == id);
if (matchingIndex !== -1) {
newState.todos[matchingIndex] = {
...newState.todos[matchingIndex],
done: !newState.todos[matchingIndex].done
}
}
// set new state
setState(newState);
}
Something similar to D. Smith's answer but a little more concise:
const toggleDone = (id) => {
setState(prevState => {
// Loop over your list
return prevState.map((item) => {
// Check for the item with the specified id and update it
return item.id === id ? {...item, done: !item.done} : item
})
})
}
All the great answers but I would do it like this
setState(prevState => {
...prevState,
todos: [...prevState.todos, newObj]
})
This will safely update the state safely. Also the data integrity will be kept. This will also solve the data consistency at the time of update.
if you want to do any condition do like this
setState(prevState => {
if(condition){
return {
...prevState,
todos: [...prevState.todos, newObj]
}
}else{
return prevState
}
})
I would create just the todos array using useState instead of another state, the key is creating a copy of the todos array, updating that, and setting it as the new array.
Here is a working example: https://codesandbox.io/s/competent-bogdan-kn22e?file=/src/App.js
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
title: "take out trash",
done: false
},
{
id: 2,
title: "wife to dinner",
done: false
},
{
id: 3,
title: "make react app",
done: false
}
]);
const toggleDone = (e, item) => {
const indexToUpdate = todos.findIndex((todo) => todo.id === item.id);
const updatedTodos = [...todos]; // creates a copy of the array
updatedTodos[indexToUpdate].done = !item.done;
setTodos(updatedTodos);
};
I have managed to create my function to select a single or multiple boxes. On the single ones I don't have any issue, I mean when you click a box it is checked or unchecked.
But it is not the same case with the select all. Any ideas how can I fix that? (I am using material-ui library for my boxes, but basically they are simple HTML input)
Select all component and function:
<Checkbox
name="checkboxes"
checked={this.state.allCheckboxes}
onChange={this.handleAllCheckboxes}
indeterminate
/>Select All
Function:
handleAllCheckboxes = (e) => {
let responseObj = this.state.items;
let prepareObj = {};
if(e.target.checked){
//to do add loop to check all check box
responseObj.forEach(function(item){
if(item.documentId !== null && item.documetNumber !== null ){
prepareObj[item.documentId] = item.documentNumber;
// this.refs.documentId
}
});
let toSee = Object.keys(prepareObj).length > 0 ? true : false;
this.setState({
docList: prepareObj,
visible: toSee
})
let checkboxes = document.getElementsByName('DocCheckbox')
checkboxes.forEach(function(checkbox){
checkbox.checked = checkbox.checked
})
console.log(checkboxes)
} else {
//to do add loop to uncheck all check box
this.setState({
prepareObj: {}
})
}
console.log(prepareObj);
};
Select single component and function:
<Checkbox
name='DocCheckbox'
type='checkbox'
color='default'
value={JSON.stringify({ documentId: rowData.documentId, documentNumber: rowData.documentNumber })}
onClick={this.handleCheckboxClick}/>
Function:
handleCheckboxClick = (e, id) => {
if (id) {
} else {
let parsedVal = JSON.parse(e.target.value);
// console.log(e.target.value)
let newDocList = { ...this.state.docList };
if (e.target.checked) {
this.setState({
singleCheckbox:true
})
newDocList[parsedVal.documentId] = parsedVal.documentNumber;
} else {
delete newDocList[parsedVal.documentId];
this.setState({
singleCheckbox:false
})
}
let toSee = Object.keys(newDocList).length > 0 ? true : false;
this.setState(
{
docList: newDocList,
visible: toSee
},
() => {
console.log(this.state.docList);
}
);
}
};
UPDATE
Answer of my code based to the reply of #norbitrial
( The answer is based on my properties , calls and data so feel free to modify it for your purpose )
Step 1 - Create a constructor to maintain your data and global checked state
constructor(props) {
super(props);
this.state = {
docList: {},
checked: false,
hasToCheckAll: false
}
}
Step 2 - Create functions to handle single and multiple checkboxes
Handle single checkbox
handleCheckboxClick = (clickedItem) => {
console.log(clickedItem)
// let parsedVal = JSON.parse(e.target.value);
let newDocList = { ...this.state.docList };
if (!clickedItem.checked) {
newDocList[clickedItem.documentId] = clickedItem.documentNumber;
console.log(newDocList)
} else {
delete newDocList[clickedItem.documentId];
}
let toSee = Object.keys(newDocList).length > 0 ? true : false;
console.log(toSee)
this.setState(
{
docList: newDocList,
visible: toSee
}, ()=>{
console.log(newDocList)
});
const updatedArray = this.state.items.map((item) => {
item.checked = item.documentId === clickedItem.documentId ? !item.checked : item.checked;
return item;
});
this.setState({
items: updatedArray,
});
};
Handle all checkboxes
handleAllCheckboxes = (e) => {
const hasToCheckAll = !this.state.hasToCheckAll;
const updatedArray = this.state.items.map((item) => {
item.checked = hasToCheckAll;
return item;
});
console.log(updatedArray)
let responseObj = this.state.items;
let prepareObj = {};
if (e.target.checked) {
//to do add loop to check all check box
responseObj.forEach(function (item) {
if (item.documentId !== null && item.documetNumber !== null) {
prepareObj[item.documentId] = item.documentNumber;
}
});
let toSee = Object.keys(prepareObj).length > 0 ? true : false;
console.log(toSee)
this.setState({
docList: prepareObj,
// allCheckboxes: true,
items: updatedArray,
hasToCheckAll,
visible: toSee
})
let checkboxes = document.getElementsByName('checkAll')
checkboxes.forEach(function (checkbox) {
checkbox.checked = e.target.checked
})
console.log(checkboxes)
} else {
console.log(updatedArray)
this.setState({
docList: {},
hasToCheckAll:false
})
}
};
Step 3 - Insert the state of the checked boxes inside into the object . And loop over them ( again this is coming from the response from my Back End so for everyone this step will be different )
.then((response) => {
// handle success
let dataItem = response.data.bills;
let prepareDataItem = [];
dataItem.forEach(function (item) {
item.checked = false;
prepareDataItem.push(item);
})
Step 4 - Render the checkboxes (these checkboxes are based on material-ui library , but it can work for a simple inputs also)
<Checkbox
name='DocCheckBox'
type='checkbox'
checked={rowData.checked}
color='default'
value={JSON.stringify({ documentId: rowData.documentId, documentNumber: rowData.documentNumber })}
onChange={() => this.handleCheckboxClick(rowData)}/>
<Checkbox
name="checkboxes"
checked={this.state.hasToCheckAll}
onChange={this.handleAllCheckboxes}
indeterminate
/>Select All
I would change a bit how you are handling these things in the application.
Technically you are manipulating the DOM directly, instead of leaving it to React with its states. In this case there is a definitely better way to handle checkbox states in the UI.
The solution:
1. Changed default state:
Let me state that I don't have the real data structure what you have so I have the following in the constructor just for the representation:
constructor(props:any) {
super(props);
this.state = {
items: [
{ documentId: 1, documentNumber: 1234, checked: false },
{ documentId: 2, documentNumber: 1235, checked: false },
{ documentId: 3, documentNumber: 1236, checked: false },
],
hasToCheckAll: false,
}
}
As you can see the items have checked property in order to handle them in the state.
2. Rendering the checkboxes differently
In the render function I have changed couple of things:
render() {
return (
<div>
<Checkbox
name="checkboxes"
checked={this.state.hasToCheckAll}
onChange={() => this.handleAllCheckboxes()}
indeterminate
/>Select All
{this.state.items.map((item:any) => {
return <div key={item.documentNumber}>
<Checkbox
name="DocCheckbox"
color="default"
checked={item.checked}
value={JSON.stringify({ ...item.documentId, ...item.documentNumber })}
onChange={() => this.handleCheckboxClick(item)}/> {item.documentNumber}
</div>
})}
</div>
)
}
3. Handling checkbox state is also changed:
Take a look at the following handlers:
handleAllCheckboxes = () => {
const hasToCheckAll = !this.state.hasToCheckAll;
const updatedArray = this.state.items.map((item:any) => {
item.checked = hasToCheckAll;
return item;
});
this.setState({
...this.state,
items: updatedArray,
hasToCheckAll: hasToCheckAll,
});
};
handleCheckboxClick = (clickedItem:any) => {
const updatedArray = this.state.items.map((item:any) => {
item.checked = item.documentId === clickedItem.documentId ? !item.checked : item.checked;
return item;
});
this.setState({
...this.state,
items: updatedArray,
});
};
The result:
Of course you can extend this example with your data manipulation if there is a further need. The solution has been built with TypeScript - I had a project opened with that - but you can remove types where it has been added.
The above example works like charm, I hope this helps!