Related
I am using an antd tree component my issue is if you search something in the search bar then you will get search results in that result if you check and up check any field what happens all previous data get unchecked whatever data is present into the search bar result that only data remain selected if it already select or you just select what my task is I don't want to get unchecked all previously selected checked that only field update that we change right now I don't have any idea how can I fix this if anybody knows anyway, also I added a complete code SandBox link below.
This is my search bar filter code
const hasSearchTerm = (n, searchTerm) =>
n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
const filterData = (arr, searchTerm) =>
arr?.filter(
(n) =>
hasSearchTerm(n.title, searchTerm) ||
filterData(n.children, searchTerm)?.length > 0
);
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const filteredData = searchString
? filterData(data, searchString).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: filterData(n.children, searchString, checkedKeys)
};
})
: data;
setExpandedTree([...keysToExpand]);
return filteredData;
}
This issue happens when the check or unchecks field after searching in the search bar in this part of the code
const onCheck = (checkedKeysValue) => {
console.log("onCheck", checkedKeysValue);
setCheckedKeys(checkedKeysValue);
};
const Demo = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [checkedKeys, setCheckedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [tree, setTree] = useState(treeData);
const onExpand = (expandedKeysValue) => {
console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeysValue);
setAutoExpandParent(false);
};
const onCheck = (checkedKeysValue) => {
console.log("onCheck", checkedKeysValue);
setCheckedKeys(checkedKeysValue);
};
const onSelect = (selectedKeysValue, info) => {
console.log("onSelect", info);
setSelectedKeys(selectedKeysValue);
};
React.useEffect(() => {
const checked = [];
treeData.forEach((data) => {
data.children.forEach((item) => {
if (item.checked) {
checked.push(item.key);
}
});
});
setCheckedKeys(checked);
}, []);
React.useEffect(() => {
if (searchValue) {
const filteredData = filteredTreeData(
treeData,
searchValue,
checkedKeys,
setExpandedKeys
);
setTree([...filteredData]);
} else {
setTree(treeData);
// setExpandedKeys([]);
}
}, [searchValue, checkedKeys]);
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={tree}
/>
</div>
);
};
CodeSandBox Link
Edited Response to fix uncheck issue
If I understood the question correctly, Is the issue with the fact that previous checked items are getting cleared on search and selection of new one?
I think the solution would be to use 2 different separate trees one for filtered and the other for normal.
Did some code changes on top of the sandbox code shared.
I have added a new tree when searchedValue is present.
On checking/unchecking the new filtered tree, the actual entire tree's checked values get updated
When the searched value is empty it would show the actual entire tree
Created a filteredKeys list to solve uncheck issue. Now I am able to select and unselect.
If you play around and refactor a bit, you should be able to achieve what you want.
Adding the same code below.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";
const { Search } = Input;
const treeData = [
{
title: "AP Watchlists",
key: "AP Watchlists",
children: [
{ title: "Colo Open Data", key: "Colo Open Data", checked: true },
{
title: "Department and trade",
key: "Department and trade",
checked: true
},
{
title: "North List",
key: "North List",
checked: true
},
{ title: "People's Daily", key: "People's Daily", checked: true }
]
},
{
title: "Af Watchlists",
key: "Af Watchlists",
children: [
{
title: "Service Wanted List",
key: "Service Wanted List",
checked: true
}
]
},
{
title: "EM Watchlists",
key: "EM Watchlists",
children: [
{
title: "National Financing",
key: "National Financing",
checked: true
},
{
title: "Arabia List",
key: "Arabia List",
checked: true
}
]
},
{
title: "Assets List",
key: "Assets List",
children: [
{
title: "National List",
key: "National List",
checked: true
}
]
},
{
title: "New Watchlists",
key: "New Watchlists",
children: [
{ title: "FATR", key: "FATR", checked: true },
{ title: "Internal", key: "Internal", checked: true },
{
title: "OC List (Covers 73 Lists)",
key: "OC List (Covers 73 Lists)",
checked: true
},
{ title: "UN", key: "UN", checked: true },
{
title: "Security List (Covers 18 Lists)",
key: "Security List (Covers 18 Lists)",
checked: true
}
]
}
];
const Demo = () => {
const hasSearchTerm = (n, searchTerm) =>
n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
const filterData = (arr, searchTerm, keys) => {
const result = arr?.filter(
(n) =>
hasSearchTerm(n.title, searchTerm) ||
filterData(n.children, searchTerm, keys)?.length > 0
);
result &&
result.forEach((node) => {
if (keys.indexOf(node?.key) === -1) keys.push(node?.key);
});
return result;
};
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const keys = [];
const filteredData = searchString
? filterData(data, searchString, keys).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: filterData(n.children, searchString, keys)
};
})
: data;
setExpandedTree([...keysToExpand]);
setFilteredKeys(keys);
return filteredData;
}
const [expandedKeys, setExpandedKeys] = useState([]);
const [checkedKeys, setCheckedKeys] = useState([]);
const [filteredCheckedKeys, setFilteredCheckedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [tree, setTree] = useState(treeData);
const [filteredTree, setFilteredTree] = useState([]);
const [filteredKeys, setFilteredKeys] = useState([]);
const onExpand = (expandedKeysValue) => {
// console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeysValue);
setAutoExpandParent(false);
};
const onCheck = (checkedKeysValue) => {
// console.log("onCheck", checkedKeysValue);
// console.log("checkedKeys", checkedKeys);
setCheckedKeys(checkedKeysValue);
};
const onFilteredTreeCheck = (checkedKeysValue) => {
// console.log("onFilterCheck", checkedKeysValue);
// console.log("filteredcheckedKeys", filteredCheckedKeys);
setFilteredCheckedKeys(checkedKeysValue);
const baseTreeKeys = [...checkedKeys].filter(
(node) => filteredKeys.indexOf(node) === -1
);
console.log("baseTreeKeys", baseTreeKeys);
console.log("checkedKeysValue", checkedKeysValue);
setCheckedKeys([...checkedKeysValue, ...baseTreeKeys]);
};
const onSelect = (selectedKeysValue, info) => {
console.log("onSelect", info);
setSelectedKeys(selectedKeysValue);
};
// React.useEffect(() => {
// const checked = [];
// treeData.forEach((data) => {
// data.children.forEach((item) => {
// if (item.checked) {
// checked.push(item.key);
// }
// });
// });
// setCheckedKeys(checked);
// }, []);
React.useEffect(() => {
setFilteredKeys([]);
if (searchValue) {
const filteredData = filteredTreeData(
treeData,
searchValue,
checkedKeys,
setExpandedKeys
);
setFilteredTree([...filteredData]);
} else {
setTree(treeData);
// setExpandedKeys([]);
}
}, [searchValue, checkedKeys]);
console.log("filteredCHeckedValues", filteredCheckedKeys);
console.log("existing checked values", checkedKeys);
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
{searchValue ? (
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onFilteredTreeCheck}
checkedKeys={filteredCheckedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={filteredTree}
/>
) : (
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={tree}
/>
)}
</div>
);
};
ReactDOM.render(<Demo />, document.getElementById("container"));
Let me know if this solves your issue. I am able to select multiple values in subsequent searches without loosing the checked ones.
As mentioned in the API document, filterTreeNode will keep keys from the tree node, and will not hide it.
filterTreeNode
Defines a function to filter treeNodes. When the function returns true, the corresponding treeNode will be checked
If you want to hide tree node, you will have to manually filter it first before before passing it to Tree in loop function, something like:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";
const gData = [
{
key: "1",
title: "title 1"
},
{
key: "2",
title: "title 2"
},
{
key: "3",
title: "title 3",
children: [
{
key: "4",
title: "title 4"
},
{
key: "5",
title: "title 5",
children: [
{
key: "6",
title: "title 6"
},
{
key: "7",
title: "title 7"
}
]
}
]
}
];
const { Search } = Input;
const dataList = [];
const generateList = (data) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const SearchTree = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [treeData, setTreeData] = useState(gData);
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onChange = (e) => {
const value = e.target.value?.toLowerCase();
const expandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
if (value) {
const hasSearchTerm = (n) => n.toLowerCase().indexOf(value) !== -1;
const filterData = (arr) =>
arr?.filter(
(n) => hasSearchTerm(n.title) || filterData(n.children)?.length > 0
);
const filteredData = filterData(gData).map((n) => {
return {
...n,
children: filterData(n.children)
};
});
setTreeData(filteredData);
setExpandedKeys(expandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
} else {
setTreeData(gData);
setExpandedKeys([]);
setSearchValue("");
setAutoExpandParent(false);
}
};
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = title.indexOf(searchValue) !== -1 ? true : false;
console.log(searchValue);
console.log(result);
return result;
};
const loop = (data) =>
data.map((item) => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key
};
});
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={onChange}
/>
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(treeData)}
filterTreeNode={filterTreeNode}
/>
</div>
);
};
ReactDOM.render(<SearchTree />, document.getElementById("container"));
I have an array of objects and I am trying to group the objects by type, like this:
const el = {
Type1: [
{
param1: 1,
param2: value1,
position: 1,
type: 'Type1'
}, {
param1: 2,
param2: value2,
position: 2,
type: 'Type1'
}
],
Type2: [
{
param1: 3,
param2: value3,
position: 1,
type: 'Type2'
}, {
param1: 4,
param2: value4,
position: 2,
type: 'Type2'
}, {
param1: 5,
param2: value5,
position: 3,
type: 'Type2'
}
]
}
The objects in Type property represent choices for radio buttons.
This way I set the first choice checked by default:
const groupBy = (array, key) => {
return array.filter(o => o.checked != undefined).reduce((result, currentValue) => {
(result[currentValue[key]] = result[currentValue[key]] || []).push(
currentValue);
return result;
}, {});
};
const [state, setState] = useState({
buttonsTypes: groupBy([...arrayObjects, arrayObjects.map((item, index) => item.position === 1 ? item['checked'] = true : item['checked'] = false)], 'type'),
selections: arrayObjects.filter(item => item.checked)
});
My question is how to map this dynamically and change the checked value on click?
I tried this so far:
const selectChoice = (obj) => {
setState(prevState => {
return {
buttonsTypes: ... // Not sure how to update the checked value only for the selection of the specific obj.Type
selections: ...
};
});
};
{Object.entries(state.buttonsTypes).map((item, index) => {
return <React.Fragment>
<div className="some-class"/>
<span id="text">{item[0]}</span>
<div id="radio-button-wrapper">
{item[1].map((choice, index) => {
return <label className="label-css">
<input type="radio" name={choice.param2} value={choice.param1} checked={choice.checked} onClick={() => selectChoice(choice)}/>
</label>
})
}
</div>
</React.Fragment>
})
}
Can anyone suggest some examples or another approach on how to solve this?
Update: As suggested in the answer, I solved it this way by keeping only an array of the selected choices for each radio group:
const selectChoice = (obj) => {
setState(prevState => {
const sel = prevState.selections;
const idx = sel.findIndex(item => item.type === obj.type);
if (idx === -1) {
sel.push(obj);
} else {
sel[idx] = obj;
}
return {
...prevState,
selections: sel
};
});
};
Inside the mapping:
{Object.entries(state.groupedArray).map((item, index) => {
return <React.Fragment>
<div className="some-class"/>
<span id="text">{item[0]}</span>
<div id="radio-button-wrapper">
{item[1].map((choice, index) => {
return <label className="label-css">
<input type="radio" name={choice.param2} value={choice.param1} defaultChecked={choice.position === 1} onClick={() => selectChoice(choice)}/>
</label>
})
}
</div>
</React.Fragment>
})
}
Would appreciate if anyone has any suggestions or comments for improvements.
Why not use 'state' to keep track of the index of the checked radio button?
Then inside of {item[1].map((choice, index) => {...}}
you can check whether the currently saved 'state' matches the current index in the input you're trying to return
and update the index of the currently checked radio input by attaching your selectChoice handler
I thinking for few days but cant realize how can i make it. I have 4 json data and 4 picker.
Its for city,district,village,neirborhood. In first i must choose city then in second picker it must show district about that i choose city. When i choose district from picker third one must show villages about that district. And neirborhood is same too. In that json datas they have some connection. Like city json have ' id-name' district have 'id-cityid-name' village have 'id-districtid-name' neirborhood have 'id-villageid-name' Like that. But i cant figure out how can i make it. Its my codes I really stuck with that hardly i need some help please. Thank you! My codes :
Elements :
const DropdownElements = [
{
key: 1,
title: "Şehir",
placeholder: "Şehir Seçiniz",
apiUrl: "https://api.npoint.io/995de746afde6410e3bd",
type: "city",
selecteditem: "",
data : [],
},
{
key: 2,
title: "İlçe",
placeholder: "İlçe Seçiniz",
apiUrl: "https://api.npoint.io/fc801dbd3fc23c2c1679",
type: "district",
selecteditem: "",
data : [],
},
{
key: 3,
title: "Köy",
placeholder: "Köy Seçiniz",
apiUrl: "https://api.npoint.io/72cf025083b70615b8bb",
type: "village",
selecteditem: "",
data : [],
},
{
key: 4,
title: 'Mahalle',
placeholder:'Mahalle Seçiniz',
apiUrl: 'https://api.npoint.io/0c04c63923c8ca4e117b',
type: 'neighborhood',
selecteditem: "",
data : [],
},
];
Component :
const PickerCompanent = (props) => {
const [xdata, setData] = useState([]);
const [newData, setNewData] = useState([]);
let x;
let y = [];
// data.filter((a) => a.il_id == "3");
useEffect(() => {
props.datasource.then(setData);
switch (props.type) {
case "city":
x = props.selecteditem;
setNewData(xdata);
break;
case "district":
y = xdata.filter((element) => {
if (props.selecteditem === element.id) {
return element;
}
});
break;
case "village":
console.log("village");
break;
default:
console.log("def");
break;
}
}, [props.datasource]);
return (
<Select
showSearch
style={{ width: 200, marginLeft: 15 }}
placeholder={props.placeholder}
optionFilterProp="children"
onChange={(x) => props.onChange(x)}
onFocus={props.onFocus()}
datasource={xdata}
onSearch={props.onSearch()}
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{xdata &&
xdata.map((x) => {
return (
<Select.Option key={x.id} value={x.id}>
{x.name}{" "}
</Select.Option>
);
})}
</Select>
);
};
My App :
const App = () => {
const [dataap, setDataAp] = useState([]);
const [idhold, setIDHold] = useState();
const filldata = (value) => {};
function onChange(value) {
setIDHold(value);
console.log(value);
}
const getData = (value, type) => {
return fetch(value)
.then((x) => x.json())
.then((y) => {
return y;
});
};
function onFocus() {}
function onSearch(val) {}
return (
<Space>
{DropdownElements.map((x) => {
return (
<PickerCompanent
showSearch
selecteditem={idhold}
key={x.key}
placeholder={x.placeholder}
type={x.type}
datasource={getData(x.apiUrl)}
onFocus={onFocus}
onChange={(z) => onChange(z)}
onFocus={onFocus}
onSearch={onSearch}
></PickerCompanent>
);
})}
</Space>
);
};
If you need i can give my teamviewer or skype too. I really need that help thanks for replies!
Sandbox : codesandbox.io/s/runtime-monad-vxit
https://codesandbox.io/s/mystifying-moore-7w105?file=/src/App.js
Select CityTwo to see the dropdown update.
You need a switch. Updating arrays inside state is tricky. You can't populate or push anything in an array that's in state. Update your array outside state, THEN update state.
I am using Ant Design for my React project and I'm having trouble with the Table component. I have a list of tasks to which I add a new task based on a Form content - currently just by adding to an array of objects (taskListMock in the code snippets), the app is not linked to any backend. The form works fine, however, the Table does not refresh, even though the dataSource prop of the Table gets its content directly from the state and the state updates correctly - confirmed by logging and devtools. Curiously, the table refreshes with the new task when I initiate the implemented sorting, so my suspicion is that the Table somehow does not refresh its content from the state change, only on onChange hooks or something, but I'm feeling in a bit of a dead-end - any help would be greatly appreciated since I'm planning to use similar functionality in other Tables.
The structure is pretty simple, I have a TasksIndex.js with the Table as an individual component in TaskListTable.js
TaskListTable.js:
const TaskListTable = (props) => {
const { t } = useTranslation();
const [tableContent, setTableContent] = useState(props.tasks);
return (
<React.Fragment>
<Table
pagination={false}
dataSource={tableContent}
columns={[
{
title: t("tasks.name"),
key: "name",
render: (text) => {
return <p>{text.slug}</p>;
},
},
{
title: t("tasks.dateDue"),
dataIndex: "dateDue",
key: "dateDue",
sorter: (a, b) =>
new Date(a.dateDue).getTime() - new Date(b.dateDue).getTime(),
render: (dateDue) => {
let dateFormatted = moment(dateDue);
return <>{dateFormatted.format("LL")}</>;
},
defaultSortOrder: "ascend",
},
{
title: t("tasks.priority"),
key: "priority",
dataIndex: "priority",
render: (priority) => (
<React.Fragment>
{priority === "low" ? (
<Tag color="geekblue">{t("tasks.lowPriority")}</Tag>
) : (
""
)}
{priority === "normal" ? (
<Tag color="green">{t("tasks.normalPriority")}</Tag>
) : (
""
)}
{priority === "high" ? (
<Tag color="volcano">{t("tasks.highPriority")}</Tag>
) : (
""
)}
</React.Fragment>
),
sorter: (a, b) => {
const priorityOrder = ["low", "normal", "high"];
return (
priorityOrder.indexOf(a.priority) -
priorityOrder.indexOf(b.priority)
);
},
},
{
title: t("tasks.options"),
key: "options",
render: (item) => {
return (
<Checkbox value={item.id}>{t("tasks.setCompleted")}</Checkbox>
);
},
},
]}
></Table>
</React.Fragment>
);
};
export default TaskListTable;
TaskIndex.js:
const TasksIndex = () => {
const [isModalOpen, setModalOpen] = useState(false);
const [taskList, updateTaskList] = useState(taskListMock);
const [form] = Form.useForm();
const addTask = useCallback(
(values) => {
const newTaskList = taskList;
newTaskList.push({
id: taskList[taskList.length - 1] + 1,
slug: values.name,
description: values.description,
dateDue: values.dateDue.format("YYYY-MM-DD"),
priority: values.priority,
checked: false,
});
form.resetFields();
updateTaskList(newTaskList);
closeModal();
},
[taskList, form]
);
const openModal = () => {
setModalOpen(true);
};
const closeModal = () => {
setModalOpen(false);
};
const { t } = useTranslation();
return (
<React.Fragment>
<Title>{t("tasks.tasksOverviewHeader")}</Title>
<Row gutter={[16, 24]}>
<Col className="gutter-row" span={24}>
<TaskListTable tasks={taskList}></TaskListTable>
</Col>
</Row>
...
...
I finally fixed it - it seems that creating a new array and pushing the new task to it was not considered a state change (or perhaps a Table change trigger), unlike using the spread operator. The working code looks like this:
const addTask = (values) => {
const newTask = {
id: taskList[taskList.length - 1] + 1,
slug: values.name,
description: values.description,
dateDue: values.dateDue.format("YYYY-MM-DD"),
priority: values.priority,
checked: false,
};
updateTaskList([...taskList, newTask]);
closeModal();
form.resetFields();
};
I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues