the way to use setState with object - reactjs

import React, {useState} from "react";
const SideListItem = () => {
const [showItem, setShowItem] = useState([
{id: "List A", clicked: true},
{id: "List B", clicked: true},
{id: "List C", clicked: true},
]);
const clickList = () => {
const value = showItem[0].clicked;
setShowItem(() => {
const boolValue = value? false: value === true;
return boolValue;
});
return console.log(value);
};
I want to make the next process below.
when I click a button, the value of state is chaged.
=> if it is "true", then it changed in "false". And if "false", then "true".
But, my code didn't work... When I used state with number, string, boolean, It worked.
Is there a way to use state with object?
Thank you in advance!
I tried this code.
const [clicked, setClicked] = useState(false);
const clickList = () => setClicked(!clicked);
But, I want to use state with object.

You can do it like this and add more statements if needed:
const clickList = () => {
setShowItem(prevState => {
return prevState.map(item => {
if (item.id === "List A") {
return {...item, clicked: !item.clicked};
}
return item;
});
});
};
But it's better to get the id as param like this:
const clickList = (id) => {
setShowItem(prevState => {
return prevState.map(item => {
if (item.id === id) {
return {...item, clicked: !item.clicked};
}
return item;
});
});
};

You can handle it in this way :
const clickList = () => {
setShowItem(prevState => {prevState.map(item=>({...item,clicked:!item.clicked})));
};

You can transform the previous state of the array to one where the clicked property of the clicked item is toggled. Since you didn't provide any html I added some example html, swap it out for your own markup and use the onClick event as such:
const { useCallback, useState } = React;
const SideListItem = () => {
const [showItem, setShowItem] = useState([
{id: "List A", clicked: true},
{id: "List B", clicked: true},
{id: "List C", clicked: true},
]);
const handleClick = useCallback(id => {
setShowItem(p => p.map(item => {
if (item.id === id) {
return {
...item,
clicked: !item.clicked
}
}
return item;
}));
}, []);
return showItem.map(item =>
<div key={item.id} onClick={() => handleClick(item.id)}>
Clicked: {item.clicked ? "yes" : "no"}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<SideListItem />
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

Related

How can I display maximum item selected once i have selected 3 items from dropdown

I have multiselect dropdown items with checkboxes in my react app, what I am trying to achieve is if I checked any three items, then dropdown should display maximum items selected and if I unchecked anyone of them, it should display back the drop down box with items.
Somehow it doesn't work, could someone please advise.
CodeSanbox link
https://codesandbox.io/s/musing-sun-swvj6y?file=/src/App.js
import { useState } from "react";
import Multiselect from "multiselect-react-dropdown";
import "./styles.css";
export default function App() {
const options = [
{ key: "Apple", email: "apple#test.com", id: 1 },
{ key: "Orange", email: "oranges#test.com", id: 2 },
{ key: "Mango", email: "mango#test.com", id: 3 },
{ key: "Grapes", email: "grapes#test.com", id: 4 }
];
const [option, setOption] = useState([]);
const [selectedOption, setSelectedOption] = useState([]);
const [maxOptions, setMaxOptions] = useState(0);
const handleTypeSelect = (e, i) => {
const copy = [...selectedOption];
copy.push(e[3 - maxOptions]);
setSelectedOption(copy);
setMaxOptions((prevState) => prevState - 1);
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
setMaxOptions((prevState) => prevState + 1);
};
options.forEach((option) => {
option.displayValue = option.key + "\t" + option.email;
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Multiselect
onSelect={(e) => handleTypeSelect(e, selectedOption.length)}
onRemove={handleTypeRemove}
options={selectedOption.length + 1 === maxOptions ? [] : options}
// options={!showOptions ? [] : option}
displayValue="displayValue"
showCheckbox={true}
emptyRecordMsg={"Maximum fruits selected !"}
/>
</div>
);
}
The library doesn't support manually selecting/deselecting the options. Reference
There is one hack that you can do. You can play around with key. Use the selectedItems as key and then it will re-mount the component whenever the selectedItems changes.
Note that this hack is not the recommended way to do "React".
You update the options based on the size of the selected options
const maxSelectableItems = 3;
const options = [
{ key: "Apple", email: "apple#test.com", id: 1 },
{ key: "Orange", email: "oranges#test.com", id: 2 },
{ key: "Mango", email: "mango#test.com", id: 3 },
{ key: "Grapes", email: "grapes#test.com", id: 4 }
];
<Multiselect
// This will re-mount your component whenever
// selected items changes
key={selectedItems}
onSelect={handleSelection}
onRemove={handleRemove}
// This will set the pre-selected values on re-mount
selectedValues={selectedItems}
options={
selectedItems.length === maxSelectableItems
? []
: options.map((o) => ({
...o,
displayValue: `${o.key}\t${o.email}`
}))
}
displayValue="displayValue"
showCheckbox
emptyRecordMsg={"Maximum fruits selected !"}
/>
And, your handleSelection and handleRemove will look like this:
const [selectedItems, setSelectedItems] = useState([]);
const handleSelection = (selectedItems) => {
setSelectedItems(selectedItems.slice(0, maxSelectableItems));
};
const handleRemove = (selectedItems) => {
setSelectedItems(selectedItems);
};
One issue with this is that since it re-mounts the entire multi-select component, when you select/remove an item, the input will lose focus. So, you will have to manually give focus to the input after selection/removal.
const focusOnInput = () => {
setTimeout(() => {
// You can use a better selector (this is just a generic input selector)
document.querySelector("input").focus();
// Adding some delay to allow the component to re-mount
}, 10);
};
And then, use this in your selection/removal handlers
const handleSelection = (selectedItems) => {
setSelectedItems(selectedItems.slice(0, maxSelectableItems));
focusOnInput()
};
const handleRemove = (selectedItems) => {
setSelectedItems(selectedItems);
focusOnInput()
};
Here is a link to a working sandbox
import { useState, useRef } from "react";
import Multiselect from "multiselect-react-dropdown";
import "./styles.css";
export default function App() {
const options = [
{ key: "Apple", email: "apple#test.com", id: 1 },
{ key: "Orange", email: "oranges#test.com", id: 2 },
{ key: "Mango", email: "mango#test.com", id: 3 },
{ key: "Grapes", email: "grapes#test.com", id: 4 }
];
const [option, setOption] = useState([]);
const [selectedOption, setSelectedOption] = useState([]);
const fruitmultiselect = useRef(null);
options.forEach((option) => {
option.displayValue = option.key + "\t" + option.email;
});
return (
<div className="App">
<h1>Hello CodeSandbox3</h1>
<Multiselect
ref={fruitmultiselect}
onSelect={(selectedList, selectedItem) => {
if(selectedList.length === 3){
fruitmultiselect.current.toggelOptionList()
console.log("onSelect length: "+selectedList.length);
}
}}
options={options}
displayValue="displayValue"
showCheckbox={true}
selectionLimit={3}
closeOnSelect={true}
emptyRecordMsg={"Maximum fruits selected !"}
/>
</div>
);
}

How do I disable a button in react hooks that has a setState function already inside the onClick?

I am trying to disable a like button that already has an useState for incrementing the likes. And I wanted to disable the button once its clicked.
I would appreciate some help. Thank You!
const allItems = [
{
id: 1,
name: "The Rise and Decline of Patriarchal Systems",
image: "https://img.thriftbooks.com/api/images/i/m/8ECA8C9BAF351D13622ADFFBFA8A5D4E2BAABAFF.jpg",
likes: 3359,
price: 1
}
]
const Shop = () => {
const [items, setItems] = React.useState({allItems, disable: false})
const updateLike = (item) => setItems(items.map(indItem => {
if (indItem !== item) {
return indItem
}
else {
return {...item, likes: item.likes + 1}
}
}))
const listItemsToBuy = () => items.map((item) => (
<div key={item.id} className="card"></div>
<div className="font-text"><h2>{`${item.name}`}</h2>
</div>
<h2>Likes: {item.likes}</h2>
<div>
<button items={items} onClick={()=> updateLike(item, true)}> Like </button>
));```
Inside updateLike function update your state
setItems(prevState => { ...prevState, disable: true, })
Then your button will be look like
<button disabled={items.disabled} >
But preferable to have separate state for this purpose

(React) Changing True/False Value Based On Checkbox Value - Getting "TypeError: checked.map is not a function"

I am trying to change the 'checked' value to "true" or "false" based on user checkbox selection. I have the below code and I am getting TypeError: checked.map is not a function. I want the "name" key to always be the same for each object in the "checked" array, but the value for the checked property to be either true or false based on a checkbox the user clicks on. Does anyone see why I could be getting this error, and what I could do to make this work? Thanks.
const [checked, setChecked] = useState([
{ name: 'user1', checked: false },
{ name: 'user2', checked: false },
{ name: 'user3', checked: false }
]);
const handleChange = (name, id) => {
let header = id;
let updatedList = checked.map((item) => {
if (item.header === header) {
return { ...item, checked: !item.checked };
}
return item;
});
setChecked(...updatedList);
}
The main problem is that you spread the created updatedList array:
setChecked(...updatedList);
Your checked state is an array -> you should refresh it with an array. Because you spread it, then the setChecked messes up the state & you cannot map it anymore (thus .map() is not a function).
Try setChecked with a function & its argument as an array:
const {
useState
} = React
const App = () => {
const [checked, setChecked] = useState([{
name: 'user1',
checked: false
},
{
name: 'user2',
checked: false
},
{
name: 'user3',
checked: false
}
]);
const handleChange = ({ name, checked }) => {
setChecked((previousChecked) => {
return previousChecked.map(item => {
if (item.name === name) {
item.checked = checked
}
return item
})
})
}
return (
<div> {
checked.map(item => {
return (
<label>
{
item.name
}
<input type = "checkbox"
onChange = {() => handleChange({...item, checked: !item.checked})}
/>
<br />
</label>
)
})
}
{
// just so you see the change of state:
JSON.stringify(checked)
}
</div>
)
}
ReactDOM.render( <App /> , document.querySelector("#app"))
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="app"></div>
TypeError: checked.map is not a function would mean "checked" is not array.
It is possible that somewhere along the way, the setChecked is called with non-array value. You should investigate, there might be an unintended setChecked happening.
In the meantime, a quick fix is to check if it's an array before calling .map
const handleChange = (name, id) => {
let header = id;
if (Array.isArray(checked)) {
let updatedList = checked.map((item) => {
if (item.header === header) {
return { ...item, checked: !item.checked };
}
return item;
});
setChecked(...updatedList);
} else {
console.log(`checked is not array? Let's see who's behind the mask`, checked);
}
}

ReactJS: Manage multiple checkbox inputs with useState

I have the following example component that uses multiple checkboxes for choosing what items to remove from a list of objects:
import React, { useState } from "react";
import "./styles.css";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState({});
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
setAllChecked(e.target.checked);
};
const handleSingleCheck = e => {
setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
};
const onDelete = () => {
console.log(isChecked);
const newData = data.filter(
item => !Object.keys(isChecked).includes(item.name)
);
console.log(newData);
setFormData(newData);
};
return (
<div className="App">
<div>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
<label />
</div>
{formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={allChecked ? true : isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
))}
<button onClick={() => onDelete()}>DELETE</button>
</div>
);
}
This is mostly working, except for check all. It seems onChange will not update while using useState. I need to be able to select all the objects or uncheck some to mark for deletion.
Any help is greatly appreciated.
CodeSandbox Example: https://codesandbox.io/s/modest-hodgkin-kryco
UPDATE:
Okay, after some help from Richard Matsen,
Here is a new solution without direct DOM manipulation:
import React, { useState, useEffect } from "react";
import "./styles.css";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState();
const [loading, setLoading] = useState(true);
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
setAllChecked(e.target.checked);
};
const handleSingleCheck = e => {
setIsChecked({ ...isChecked, [e.target.name]: e.target.checked });
};
const onDelete = () => {
const itemList = Object.keys(isChecked).map((key:any) => {
if (isChecked[key] === true) {
return key
}
})
const result = formData.filter((item:any) => !itemList.includes(item.name))
console.log(result)
setFormData(result)
}
useEffect(() => {
if (!loading) {
setIsChecked(current => {
const nextIsChecked = {}
Object.keys(current).forEach(key => {
nextIsChecked[key] = allChecked;
})
return nextIsChecked;
});
}
}, [allChecked, loading]);
useEffect(() => {
const initialIsChecked = data.reduce((acc,d) => {
acc[d.name] = false;
return acc;
}, {})
setIsChecked(initialIsChecked)
setLoading(false)
}, [loading])
return (
<div className="App">
<div>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
<label />
</div>
{!loading ? formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
)): null}
<button onClick={() => onDelete()}>DELETE</button>
</div>
);
}
codesandbox of working solution:
https://codesandbox.io/s/happy-rubin-5zfv3
The basic problem is checked={allChecked ? true : isChecked[test.name]} stops the unchecking action from happening - once allChecked is true it does not matter what value isChecked[test.name] has, the expression is always going to be true.
You should rely only on isChecked for the value, and treat changing allChecked as a side-effect.
useEffect(() => {
setIsChecked(current => {
const nextIsChecked = {}
Object.keys(current).forEach(key => {
nextIsChecked[key] = allChecked;
})
return nextIsChecked;
});
}, [allChecked]);
...
{formData.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked[test.name]}
onChange={handleSingleCheck}
/>
</div>
))}
There's also this warning cropping up
Warning: A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
So that's basically saying don't initialize isChecked to {}, because the input's checked property is initially undefined. Use this instead,
{
test1: false,
test2: false,
test3: false,
test4: false,
test5: false,
}
or this way
const data = { ... }
const initialIsChecked = data.reduce((acc,d) => {
acc[d.name] = false;
return acc;
}, {})
export default function App() {
const [allChecked, setAllChecked] = useState(false);
const [isChecked, setIsChecked] = useState(initialIsChecked);
...
The problem with your code was how you were handling allChecked. I have made some changes to your code and it works now.
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
function App() {
const [allChecked, setAllChecked] = useState(false);
// using an array to store the checked items
const [isChecked, setIsChecked] = useState([]);
const [formData, setFormData] = useState(data);
const handleAllCheck = e => {
if (allChecked) {
setAllChecked(false);
return setIsChecked([]);
}
setAllChecked(true);
return setIsChecked(formData.map(data => data.name));
};
const handleSingleCheck = e => {
const {name} = e.target;
if (isChecked.includes(name)) {
setIsChecked(isChecked.filter(checked_name => checked_name !== name));
return setAllChecked(false);
}
isChecked.push(name);
setIsChecked([...isChecked]);
setAllChecked(isChecked.length === formData.length)
};
const onDelete = () => {
const data_copy = [...formData];
isChecked.forEach( (checkedItem) => {
let index = formData.findIndex(d => d.name === checkedItem)
delete data_copy[index]
}
)
setIsChecked([])
// filtering out the empty elements from the array
setFormData(data_copy.filter(item => item));
setAllChecked(isChecked.length && isChecked.length === data.length);
};
return (
<div className="App">
<form>
<label>All</label>
<input
name="checkall"
type="checkbox"
checked={allChecked}
onChange={handleAllCheck}
/>
{ formData.map((test, index) => (
<div
key={index}
>
<label>{test.name}</label>
<input
type="checkbox"
name={test.name}
checked={isChecked.includes(test.name)}
onChange={handleSingleCheck}
/>
</div>
))
}
<label />
</form>
<button onClick={onDelete}>DELETE</button>
</div>
);
}
I think you should merge allChecked and isChecked state vars, because they represent the same thing, but your denormalizing it by creating two different vars! I suggest to keep isChecked, and modify all its entries when you press the allChecked input. Then, you can use a derived var allChecked (defined in your component or by using useMemo hook) to know if all your checks are checked or not.
Well, after some time working I came up with:
import React, { useState } from "react";
import "./styles.css";
import { useFormInputs } from "./checkHooks";
const data = [
{
name: "test1",
result: "pass"
},
{
name: "test2",
result: "pass"
},
{
name: "test3",
result: "pass"
},
{
name: "test4",
result: "pass"
},
{
name: "test5",
result: "pass"
}
];
export default function App() {
const [fields, handleFieldChange] = useFormInputs({
checkedAll: false
});
const allcheck = () => {
const checkdata = document.querySelectorAll(".checkers").length;
const numChecks = Array.from(new Array(checkdata), (x, i) => i);
numChecks.map(item => {
console.log(item);
async function checkThem() {
let element = await document.getElementsByClassName("checkers")[item];
element.click();
}
return checkThem();
});
};
return (
<div className="App">
<div>
<label>All</label>
<input name="checkall" type="checkbox" onChange={allcheck} />
<label />
</div>
{data.map((test, index) => (
<div key={index}>
<label>{test.name}</label>
<input
className="checkers"
type="checkbox"
name={test.name}
onChange={handleFieldChange}
/>
</div>
))}
</div>
);
}
Relevent codesandbox: https://codesandbox.io/s/admiring-waterfall-0vupo
Any suggestions welcomed. Also, thanks for the help guys!

How to set checked on item in DetailsList control

I use DetailsList component from office-ui-fabric-react library:
import {DetailsList} from 'office-ui-fabric-react/lib/DetailsList';
render () {
const item = [
{value: 'one'},
{value: 'two'}
]
return (
<DetailsList
checkboxVisibility={CheckboxVisibility.always}
items={items}
selection={selection}
/>
}
How to set checked for item with value `two?
Noticed you passed a selection to DetailsList. There's a few methods in selection to do that, including:
setAllSelected(isAllSelected: boolean)
setKeySelected(key: string, isSelected: boolean, shouldAnchor: boolean)
setIndexSelected(index: number, isSelected: boolean, shouldAnchor: boolean)
In your case, you can give each value a key. And then call setKeySelected somewhere (for example, componentDidMount) to tell DetailsList to select specific items.
I looked everywhere and could never find the answer. To set selected items basically on page load do the following:
<DetailsList
columns={columns}
items={getItems()}
selection={getSelectedItems()}
/>
const getItems = () => {
const items: any[] = [];
itemList.map((item: any, i) => {
items.push({
key: i.toString(),
Name: item.Name,
Description: item.Description
});
});
return [];
};
const getSelectedItems = (): ISelection => {
const selection: Selection = new Selection();
const items = getItems();
selection.setItems(items);
selectedItemsList.map((selectedItem:
any, s) => {
selection.setKeySelected(s.toString(), true, false);
});
return selection;
};
Simply call selection.setAllSelected(true) inside useEffect if you want all the list items to be selected by default.
const selection = new Selection();
useEffect(() => {
selection.setAllSelected(true)
}, [selection]);
React Typescript (tsx) Sample.
import { DetailsList, ISelection, Selection } from '#fluentui/react/lib/DetailsList';
// ...
export const Component = () => {
const getSelectionDetails = (): string => {
const selectionCount = selection.getSelection().length;
return selectionCount
? `${selectionCount} items selected: ${selection.getSelection().map( (i: any) => i.key ).join('; ')}`
: 'No items selected';
}
const createSelection = () => {
return new Selection({
onSelectionChanged: () => setSelectionDetails(getSelectionDetails()),
getKey: (item: any) => item.key
});
};
const [selection, setSelection] = React.useState(createSelection());
const [selectionDetails, setSelectionDetails] = React.useState('');
return <>
<DetailsList
// ...
selection={selection}
selectionMode={SelectionMode.multiple}
// ...
/>
</>;
}

Resources