Hello I tried to put this condition in my data: data={filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")}
but only this part is working (displaying data where state !==Good)
matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")
or if I do: data={filtersMenu} it works (allow me to filter via my dropdowns) my data content in my table.
So putting both conditions should allow me to display content where my state!==Good and (filtersMenu && matchData).
MenuDisplay component:
export default function MenuDisplay() {
const { menuId } = useParams();
const [menus, setMenus] = useState([]);
const [taste, setTaste] = useState([
{ label: "Good", value: "Good", name: "Good", selected: false },
{ label: "Medium", value: "Medium", name: "Medium", selected: false },
{ label: "Bad", value: "Bad", name: "Bad", selected: false }
]);
...
useEffect(() => {
myapi.menusFunction(parseInt(menuId))
.then(res => {
const menus = res.data.menus;
setMenus([
...menus.filter((i) => i.state === "Good"),
...menus.filter((i) => i.state !== "Good")
])
})
.catch(err => {
console.log(err)
})
}, [menuId]);
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const matchData = (
match.filter(({ _id }) => {
return !hidden[_id];
});
const data = [
...
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const reset = (list) =>
list.map((v) => ({
...v,
selected: false
}));
const cancel = () => {
setTaste(reset(taste))
...
};
const filtersMenu = matchData.filter((menu) => {
const allTaste = taste.every(i => i.selected === false)
const selTaste = (taste.filter((e) => e.selected)).map(({ value }) => value);
const allTasteSel = selTaste.indexOf(menu.taste) >= 0
...
return allTasteSel && ...
});
return (
<div>
...
<Table
data={filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")}
columns={data}
/>
</div>
);
}
I think this statement is logically wrong:
filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")
Since when, filtersMenu && matchData && showGood is true, you want to show only the items which are filtered by your dropdown and are good, then, it should be like this:
filtersMenu && matchData && showGood ? filtersMenu.filter((i) => i.state === 'Good') : matchData
If the condition fails, you can either return matchData or filtersMenu, depending on your usecase.
Remove the brackets {} like below
columns=filtersMenu && matchData && showGood ? matchData : matchData.filter((i) => i.state !== "Good")
i also think your logic is wrong
Try like this
columns=filtersMenu && matchData && showGood ? matchData.filter((i) => i.state !== "Good" : matchData)
is it just parenthesis around (filtersMenu && matchData && showGood) ? :
data={ ( filtersMenu && matchData && showGood ) ? matchData : matchData.filter((i) => i.state !== "Good") }
Related
This is my codeSandbox Code! https://codesandbox.io/s/bold-lederberg-d2h508?file=/src/Components/Products/Products.js
Once I click in "default" I want to have the items back to the original presentation please! But I can not do it
sorting by price is working but the default option is giving me problems
import React, { useState } from "react";
import Products from "./Components/Products/Products";
import SearchInput from "./Components/SearchInput/SearchInput";
import data from "./utils/data";
const App = () => {
const [searchValue, setSearchValue] = useState("");
const [productsInfo, setProductsInfo] = useState([]);
const handleChange = (e) => {
setSearchValue(e.target.value);
};
const selectedChangeFilter = (e) => {
const { value } = e.target;
if (value === "sporting goods") {
const sportingGoods = data.filter(
(product) => product.category === "Sporting Goods"
);
setProductsInfo(sportingGoods);
}
if (value === "electronics") {
const electronicsGoods = data.filter(
(product) => product.category === "Electronics"
);
setProductsInfo(electronicsGoods);
}
if (value === "lowest price") {
const lowestPriceGoods = data.sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = data.sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
if (value === "all") {
setProductsInfo(data);
}
};
const searchProducts = (products) => {
if (searchValue.toLowerCase().trim() === "") {
setProductsInfo(products);
} else {
const seekedItem = productsInfo.filter(
(product) =>
product.name.toLowerCase().trim().includes(searchValue) ||
product.category.toLowerCase().trim().includes(searchValue)
);
setProductsInfo(seekedItem);
}
};
return (
<div>
<SearchInput
handleChange={handleChange}
searchValue={searchValue}
selectedChangeFilter={selectedChangeFilter}
/>
<Products
data={data}
searchValue={searchValue}
productsInfo={productsInfo}
searchProducts={searchProducts}
/>
</div>
);
};
export default App;
I tried this one below but doesn't work
if (value === "all") {
const copiedData = [...data]
setProductsInfo(copiedData);
}
This is because while sorting you are sorting on your original data array. So the item ordering changes in it. You can copy your data into a seperate array and sort then as shown below.
Updated Codesandbox Link - https://codesandbox.io/s/fervent-napier-3rhvs4?file=/src/App.js
if (value === "lowest price") {
const productsToSort = [...data]
const lowestPriceGoods = productsToSort.sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const productsToSort = [...data]
const highestPriceGoods = productsToSort.sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
I think it's because data.sort() change your javascript data object, and doesn't return a new array like filter.
Try by replacing data.sort() by [...data].sort()
const selectedChangeFilter = (e) => {
const { value } = e.target;
if (value === "sporting goods") {
const sportingGoods = data.filter(
(product) => product.category === "Sporting Goods"
);
setProductsInfo(sportingGoods);
}
if (value === "electronics") {
const electronicsGoods = data.filter(
(product) => product.category === "Electronics"
);
setProductsInfo(electronicsGoods);
}
if (value === "lowest price") {
const lowestPriceGoods = [...data].sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = [...data].sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
if (value === "all") {
setProductsInfo(data);
}
};
This is happening because Array.sort() method is mutable. You are sorting the data array and returning the sorted value. This is a short example of what is happening.
const fruits = ["Melon", "Avocado", "Apple"];
console.log(fruits) // ["Melon", "Avocado", "Apple"]
fruits.sort()
console.log(fruits) // [ 'Apple', 'Avocado', 'Melon' ]
In order to avoid that in your code you can destructure the data array. This will create a new array pointing it to a different place in the memory.
const selectedChangeFilter = (e) => {
const { value } = e.target;
// { . . . }
if (value === "lowest price") {
const lowestPriceGoods = [...data].sort((el1, el2) =>
el1.price.localeCompare(el2.price, undefined, { numeric: true })
);
setProductsInfo([...lowestPriceGoods]);
}
if (value === "highest price") {
const highestPriceGoods = [...data].sort((el1, el2) =>
el2.price.localeCompare(el1.price, undefined, { numeric: true })
);
setProductsInfo([...highestPriceGoods]);
}
// { . . . }
}
In my json file from my api, I have a variable apply, If apply:0, Then It should should print my data and If apply:1, Then It should hide my data (i.e row in my Table). Actually when I combine my 2 conditions, It is partially working :(applyStatus ? menus : menus.filter((i) => i.apply !== 1)) && (showGood ? menus : menus.filter((i) => i.taste !== "Good")), i.e Only the ...showGood ?... condition is working and the ...applyStatus?... not working.
Whereas if I do only :
<Table data={matchData && (applyStatus ? menus : menus.filter((i) => i.apply !== 1))> . Then, I have my data where apply:0 are displayed.
What's wrong in my code please ?
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus]=useState([])
const [showGood, setShowGood] = useState(false);
const [applyStatus, setApplyStatus] = useState(false);
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
useEffect (() => {
axios.post("",{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
useEffect (() => {
const GoodMenus = menus.filter((i) => i.taste === "Good");
const restOfMenus = menus.filter((i) => i.taste !== "Good");
setMenus([...GoodMenus, ...restOfMenus]);
}, [menus]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
show good
<Toggle value = {showGood} onChange={() => setShowGood(!showGood)} />
<Table
data={matchData &&(applyStatus ? menus : menus.filter((i) => i.apply !== 1)) &&
(showGood ? menus : menus.filter((i) => i.taste !== "Good"))}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]
The problem is actually in the usage of your parenthesis. Your current logic looks like this.
(applyStatus ?
menus :
menus.filter((i) => i.apply !== 1)
) && (showGood ?
menus :
menus.filter((i) => i.taste !== "Good")
)
If you simplify it it looks like
(Condition ? array : filteredArray) && (Condition ? array : filteredArray)
If you simplify it again it results in
array && array
When you are using && operator on two array like you are doing it will return you the second array which in your case is the array filter on i.taste !== "Good".
I tried to show it with a code sample below
const array = [{price:5, age:10},{price:3, age:8},{price:9, age:12},{price:12, age:13}];
const ageFiltered = array.filter(x => x.age < 11);
const priceFiltered = array.filter(x => x.price > 4);
console.log("Age filtered:" + JSON.stringify(ageFiltered));
console.log("Price filtered:" + JSON.stringify(priceFiltered));
const result = ageFiltered && priceFiltered;
console.log("&& operator:" + JSON.stringify(result));
const doubleFiltered = array.filter(x => x.age < 11 && x.price > 4);
console.log("Dobule filters:" + JSON.stringify(doubleFiltered));
To fix your issue your code must be update to be like this
menus.filter(i => (applyStatus ? true : i.apply !== 1) && (showGood ? true : i.taste !== "Good") )
Your code is not working because && only return the value of the last operand. So i think your code should be this
<Table
data={
matchData && menus.filter(i => {
if (applyStatus) return true;
return i.apply !== 1;
}).filter(i => {
if (showGood) return true;
return i.taste !== 'Good'
})
}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
if (menus.apply === 0) {
setApplyStatus(true)
}
if (menus.apply === 1) {
setApplyStatus(false)
}
from my understanding menus is an array so there is nothing like menus.apply so its either you loop the menus checking if apply is true. then there is no need for
===0 just check if its true(1) or false(0).
if you want to display those that has apply you can sort them by using map just after fetching them like this
useEffect (() => {
axios.post("",{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
let data_ =res.data.menus
let_apply_true = []
let apply_false = []
data_.map((data)=>{
if(data.apply){
let_apply_true.push(data)
}
//else will return those that are false
let apply_false.push(data)
})
setApplyMenus(let_apply_true) //
setNotApplyMenus(let apply_false)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
//that is just one way
//
i am trying to convert my class component to a functionnal component but my filters are not working anymore.
the main error is when I want to convert listProducts because I don't know how to define the prevState with useState for that case
how can update the state for case?
this is my code
class component
class ListShopping extends Component{
constructor(props) {
super(props);
this.state = {
size: "",
sort: "",
cartItems: [],
products: [],
filteredProducts: []
};
}
componentDidMount() {
fetch("http://localhost:8000/products")
.then(res => res.json())
.catch(err =>
fetch("db.json")
.then(res => res.json())
.then(data => data.products)
)
.then(data => {
this.setState({ products: data });
this.listProducts();
});
}
listProducts = () => {
this.setState(state => {
if (state.sort !== "") {
state.products.sort((a, b) =>
state.sort === "lowestprice"
? a.price < b.price
? 1
: -1
: a.price > b.price
? 1
: -1
);
} else {
state.products.sort((a, b) => (a.id > b.id ? 1 : -1));
}
if(state.size !== ""){
return {
filteredProducts: state.products.filter(
a => a.availableSizes.indexOf(state.size.toUpperCase()) >= 0
)
}
}
return { filteredProducts: state.products };
});
};
handleSortChange = e => {
this.setState({ sort: e.target.value });
this.listProducts();
};
handleSizeChange = e => {
this.setState({ size: e.target.value });
this.listProducts();
};
render() {
return (
<div className="container">
<h1>E-commerce Shopping Cart Application</h1>
<hr />
<div className="row">
<div className="col-md-9">
<Filter
count={this.state.filteredProducts.length}
handleSortChange={this.handleSortChange}
handleSizeChange={this.handleSizeChange}
/>
<hr />
<Products
products={this.state.filteredProducts}
/>
</div>
</div>
</div>
);
}
}
functionnal component
const ListShopping = () => {
const [data, setData] = useState({
products : [],
filteredProducts : [],
sort : '',
size : ''
})
const {products, filteredProducts, sort, size} = data;
const fetchApi = () => {
axios.get(`http://localhost:8000/products`)
.then(response => response.data)
.then(data => {
setData({...data, products: data});
})
}
const listProducts = () => {
};
const handleSortChange = e => {
setData({...e, sort: e.target.value})
listProducts();
};
const handleSizeChange = e => {
setData({...e, size: e.target.value})
listProducts();
};
useEffect(()=> {
fetchApi()
}, [])
return(
<div className="container">
<h1>E-commerce Shopping Cart Application</h1>
<hr />
<div className="row">
<div className="col-md-9">
<Filter
count={filteredProducts.length}
handleSortChange={handleSortChange}
handleSizeChange={handleSizeChange}
/>
<hr />
<Products
products={filteredProducts}
/>
</div>
</div>
</div>
)
}
Try this
const listProducts = () => {
setData(data => {
if (data.sort !== '') {
data.products.sort((a, b) =>
data.sort === 'lowestprice'
? a.price < b.price
? 1
: -1
: a.price > b.price
? 1
: -1,
);
} else {
data.products.sort((a, b) => (a.id > b.id ? 1 : -1));
}
if (data.size !== '') {
return {
...data,
filteredProducts: data.products.filter(
a => a.availableSizes.indexOf(data.size.toUpperCase()) >= 0,
),
};
}
return { ...data, filteredProducts: data.products };
});
};
Suppose you have a state like this
const [todos, setTodos] = useState([1,2,3,4,5,6,7]);
Method 1:
Now if you want to get prevState from this hook try this way
setTodos(oldTodo => {
oldTodo.push(1000);
setTodos(oldTodo)
})
this oldTodo work the same way the prevState works.
Method 2:
const listProducts = () => {
let oldState = todos
//Now do what you want to do with it. modify the oldTodo
setTodos(oldTodo);
}
I prefer the second method because it gives me more flexibility to change modify the state and if you cannot manage the state properly in the first method or if it finds any bug then it will return undefined so I prefer to work taking the whole state in a temporary variable and after work is done set it
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
I wrote a number input with thousand separator, everything is so smooth util I press a number than press backspace and press a number again (do it fast), it causes an infinite loop where my input value keep changing.
Please have a look and let me know where is the failure in my logic.
import React from 'react';
import { TextInput } from 'react-native';
const addSeparator = (input, divider) => {
return Number(input.toString())
.toFixed(0)
.replace(/(\d)(?=(\d{3})+(?!\d))/g, `$1${divider || '.'}`);
};
const removeNonDigits = (value) => {
const formatedValue = !value && value !== 0 ? '' : value.toString();
return formatedValue.replace(/\D/g, '');
};
const MaskedNumberInput = (
{ onChangeText, value, maxValue, ...props },
ref,
) => {
const [maskedValue, setMaskedValue] = React.useState(
value === '' ? value : addSeparator(value),
);
React.useEffect(() => {
onMaskedInputChangeText(value);
}, [value]);
React.useEffect(() => {
if (onChangeText) {
onChangeText(removeNonDigits(maskedValue));
}
}, [maskedValue]);
const onMaskedInputChangeText = (newValue) => {
let digitValue = removeNonDigits(newValue);
if (
maxValue !== '' &&
maxValue !== null &&
maxValue !== undefined &&
digitValue > maxValue
) {
digitValue = maxValue;
}
if (removeNonDigits(maskedValue) === digitValue) {
console.tron.log('return');
return;
}
const maskedDigitValue =
digitValue === '' ? digitValue : addSeparator(digitValue);
setMaskedValue(maskedDigitValue);
};
return (
<TextInput
ref={ref}
value={maskedValue}
onChangeText={onMaskedInputChangeText}
keyboardType="number-pad"
{...props}
/>
);
};