To make list of checkbox behave as radio button - reactjs

I have created a checkbox with the list of data in useState. Now I need to make the checkbox work as a radio button. what kind of logic I need to use to make a checkbox work as a radio button
import { useState } from "react";
const Checkbox = () => {
const [items, setItems] = useState([
{ id: 1, checked: false, item: "apple" },
{ id: 2, checked: false, item: "bananna" },
{ id: 3, checked: false, item: "cherry" },
{ id: 4, checked: false, item: "dragon fruit" }
]);
const handleCheck = (id) => {
const listItem = items.map((item) => item.id === id ? {...item, checked: !item.checked} : item)
setItems(listItem)
}
return(
<div>
<ul>
{
items.map((item) => (
<li key={item.id}>
<input
type="checkbox"
checked = {item.checked}
onChange = {() =>handleCheck(item.id)}
/>
<lable>{item.item}</lable>
</li>
))
}
</ul>
</div>
)
}
export default Checkbox;

Just set all the other items' checked properties to false:
const handleCheck = (id) => {
const listItem = items.map((item) =>
item.id === id
? { ...item, checked: !item.checked }
: { ...item, checked: false }
);
setItems(listItem);
};
Hope this helps.

If you want to use this as radio button logic, like selecting only one check box at a time.
while setting true for a particular checkbox, you can also set false for rest of the checkboxes
const handleCheck = (id) => {
const listItem =
items.map((item) =>
item.id === id ? {...item, checked: !item.checked} : {...item, checked: false} )
setItems(listItem)
}

A bit complicated since it's not a built-in functionality.
In order to make the checkboxes work as radio buttons, you can modify the handleCheck function to uncheck all the other items when one item is checked.
Here's one way to do that:
const handleCheck = (id) => {
const listItem = items.map((item) => {
if (item.id === id) {
return {...item, checked: !item.checked};
} else {
return {...item, checked: false};
}
});
setItems(listItem);
}
This will uncheck all the other items whenever one item is checked, so that only one item can be selected at a time, like a radio button.
Of course, there's also the built-in
<input
type="radio"
checked={item.checked}
onChange={() => handleCheck(item.id)}
/>

Related

Component not render the newest value?

I'm getting used to with redux. My problem is the itemList correctly render the latest value but the value of Checkbox which is from hook state not get the latest value. It should be checked for all item list but it is not. Although I console.log the values in the map func, it still get the latest values and the find func is correct.
export default function Component(props) {
const dispatch = useDispatch();
const { itemList } = useSelector((state) => state.AllCourses);
const [values, setValues] = useState({
all: true,
items: []
});
useEffect(() => {
dispatch(
someActions.getItemList(payload)
); //this will get latest itemList
}, []);
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
return (
<Box ml={4}>
{ itemList?.map((item) => {
return (
<Box key={item.id}>
<Checkbox
name={item.name}
value={values?.items?.find((itemVal) => item.id === itemVal.id)?.select}
/>
</Box>
);
})}
</Box>
);
}
`
Tried several solutions but still not correctly
It seems like you are using Material UI. For checkbox component you need to set the checked prop.
import Checkbox from '#mui/material/Checkbox';
const label = { inputProps: { 'aria-label': 'Checkbox demo' } };
export default function Checkboxes() {
return (
<div>
<Checkbox {...label} defaultChecked />
<Checkbox {...label} />
<Checkbox {...label} disabled />
<Checkbox {...label} disabled checked />
</div>
);
}
If you use html input tag with type checkbox, then again you have to set the checked attribute accordingly see below.
<label for="vehicle2"> I have a car</label><br>
<input type="checkbox" name="vehicle3" value="Boat" checked>
And lastly, you don't need a local state in your example and you can remove
const [values, setValues] = useState({
all: true,
items: []
});
and
useEffect(() => {
if (itemList.length) {
const newValues = {
all: true,
items: itemList.map((item) => ({
select: true,
id: item.id,
})),
};
setValues(newValues);
}
}, [itemList]);
and replace with
const values = {
all: true,
items: itemList && itemList.length ? itemList.map((item) => ({
select: true,
id: item.id,
})) : [],
};

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

Check/uncheck all checkbox implementation

I am trying to accomplish a checkbox list with an All functionality. The "ALL" toggle works to turn all the checkboxs from the list on/off. But once i try to toggle off the "ALL" checkbox once i unselect another item from the list, it doesnt work as expected, as it does not toggle off the item i selected. I used logs to see the behavior, and once the "ALL" logic kicks in, it appears that the whole list gets rerender and triggers the onChange with the new values. Any help is appreciate it
interface ContainerProps {
name: string;
flag: boolean;
}
const initList: ContainerProps[] = [
{name: "ALL", flag: false},
{name: "First item", flag: false},
{name: "Second item", flag: false},
{name: "Third item", flag: false},
{name: "Fourth item", flag: false},
]
const PodCounter: React.FC = () => {
const [propsList, setList] = useState(initList)
const onClick = (value: string, check: boolean) => {
const tempList = [...propsList]
if (value === "ALL" && check === true) {
tempList.forEach(function(item){
item.flag = check
})
} if (value != "ALL" && tempList[0].flag === true && check === false){
tempList[0].flag = check
}else {
tempList.forEach(function(item){
if (item.name === value){
item.flag = check
}
})
}
console.log("tempList", tempList)
setList(tempList)
}
return (
<IonList>
{propsList.map((obj, index)=>(
<IonItem key={index}>
<IonLabel>{obj.name}</IonLabel>
<IonCheckbox slot="end" value={obj.name} checked={obj.flag} onIonChange={e => onClick(e.detail.value, e.detail.checked)}></IonCheckbox>
</IonItem>
))}
</IonList>
);
};
This is what the app/console looks like when i clicked the "ALL" checkbox
You may separate "check/uncheck all" logic from the rest of your components and keep your app more SOLID, if I may say so:
const { useState } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const Checkbox = ({value, checked, label, onChange}) => {
return (
<label>
<input type="checkbox" {...{value, checked, onChange}} />
{label}
</label>
)
}
const CheckOptions = ({options}) => {
const allUnset = options.reduce((acc, {value}) =>
({...acc, [value]: false}), {})
const allSet = options.reduce((acc, {value}) =>
({...acc, [value]: true}), {})
const [checkedOptions, setCheckedOptions] = useState(allUnset)
const toggleOption = value =>
setCheckedOptions({...checkedOptions, [value]: !checkedOptions[value]})
const toggleAll = () => {
const isAnythingChecked = Object.values(checkedOptions).some(Boolean)
isAnythingChecked
? setCheckedOptions(allUnset)
: setCheckedOptions(allSet)
}
return !!options.length && (
<ul>
{
options.map(({value, label}) => (
<li>
<Checkbox
key={value}
checked={checkedOptions[value]}
onChange={() => toggleOption(value)}
{...{value, label}}
/>
</li>
))
}
<li>
<Checkbox
value="all"
checked={Object.values(checkedOptions).some(Boolean)}
onChange={toggleAll}
label="ALL"
/>
</li>
</ul>
)
}
const options = [{value: 'a', label: 'Option A'}, {value: 'b', label: 'Option B'}, {value: 'c', label: 'Option C'}]
render (
<CheckOptions options={options} />,
rootNode
)
ul {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>

react-select - How to ensure the default selected items aren't appearing in both dropdowns?

In react-select I am using the same options for both dropdown choices. If one dog is selected in one drop-down, then it shouldn't show in the other drop-down - which was working fine.
Now I have added default selected items, it's not working as exspected. In the first dropdown, 'Bulldog' should not be available to select. In the second dropdown, 'Chihuahua' should not be available to select, as they are both have already been selected (see on demo).
Its a fairly complicated UI and a bit out of my comfort zone. Any ideas on how to get this working?
Dropdown data & Component:
export const dogOptions = [
{ value: 1, label: "Chihuahua", firstDropdown: true, secondDropdown: false },
{ value: 2, label: "Bulldog", firstDropdown: false, secondDropdown: true },
{ value: 3, label: "Dachshund", firstDropdown: true, secondDropdown: false },
{ value: 4, label: "Akita", firstDropdown: false, secondDropdown: false },
{ value: 5, label: "Lab", firstDropdown: false, secondDropdown: false }
];
export default () => {
const [options1, setOption1] = React.useState(options);
const [options2, setOption2] = React.useState(options);
return (
<>
<p>First dropdown</p>
<Select
isMulti
options={options1}
defaultValue={options.filter((o) => o.firstDropdown === true)}
onChange={(v1) => {
setOption2(options.filter((o) => o.firstDropdown === true));
}}
/>
<div style={{ height: 30 }} />
<p>Second dropdown</p>
<Select
isMulti
options={options2}
defaultValue={options.filter((o) => o.secondDropdown === true)}
onChange={(v2, a) => {
setOption1(options.filter((o1) => !v2.includes(o1)));
}}
/>
</>
);
};
You can add an initialize callback in useState to set the initial options for both Select:
const [options1, setOption1] = React.useState(() =>
options.filter((o) => o !== defaultValue2)
);
const [options2, setOption2] = React.useState(() =>
options.filter((o) => o !== defaultValue1)
);
If both default values are the same, then it's not a valid state, you can detect and throw an exception using useEffect:
useEffect(() => {
if (defaultValue1 === defaultValue2 && defaultValue1 !== undefined) {
throw new Error("defaultValue1 and defaultValue2 must not be the same");
}
}, [defaultValue1, defaultValue2]);

(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);
}
}

Resources