Check/uncheck all checkbox implementation - reactjs

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>

Related

To make list of checkbox behave as radio button

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)}
/>

checked checkbox remain after re-rendering

i'm building a checkbox todo List that checked checkbox is disappearing.
I have two problems.
checked checkbox remains after re-rendering
When the two checkboxes are left, they dont disappear.
Here is my codeSandBox:-----
I think this might be a setState issue, setItems([...items.filter((item) => !checkedItems[item.id])]); -> rerendering (this scope havecheckedItems ={false,true,false,false,false}) so, checkbox is remaining?
import "./styles.css";
import React from "react";
const todos = [
{ id: 0, value: "Wash the dishes" },
{ id: 1, value: "have lunch" },
{ id: 2, value: "listen to music" },
{ id: 3, value: "running" },
{ id: 4, value: "work out" }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const [checkedItems, setCheckedItems] = React.useState(
new Array(todos.length).fill(false)
);
const checkHandler = (idx) => {
checkedItems[idx] = !checkedItems[idx];
setItems([...items.filter((item) => !checkedItems[item.id])]);
setCheckedItems([...checkedItems]);
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={checkedItems[idx]}
onChange={() => checkHandler(idx)}
></input>
</div>
))}
</div>
);
}
It is not good practice to use index as key when iterating objects.
Since you have id, use this.
{items.map(todo => (
<div key={todo.id}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={checkedItems[todo.id]}
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
You mess with indexes and the result is confusing.
If you want the items to be persisted and shown, just remove this line
setItems([...items.filter((item) => !checkedItems[item.id])]);
Demo
You do not need to have a separate checkedItems state. You can add a field checked in your todo object.
const todos = [
{ id: 0, value: "Wash the dishes", checked: false },
{ id: 1, value: "have lunch", checked: false },
{ id: 2, value: "listen to music", checked: false },
{ id: 3, value: "running", checked: false },
{ id: 4, value: "work out", checked: false }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const checkHandler = (idx) => {
setItems(items.filter((item) => item.id !== idx));
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={todo.checked}
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
</div>
);
}
The key mistake you're making here is in onChange={() => checkHandler(idx)} and then using idx as your variable to filter out your items. idx does NOT equal the ids you have in your todo list, it will change based on the number of items left in the array.
The filter can also be improved to just be
setItems([...items.filter((item) => item.id !== idx)]);
The final code should look something like this (I'm not sure what checkedItems is meant to be doing or what it's for so it's not a consideration in this answer).
import "./styles.css";
import React from "react";
const todos = [
{ id: 0, value: "Wash the dishes" },
{ id: 1, value: "have lunch" },
{ id: 2, value: "listen to music" },
{ id: 3, value: "running" },
{ id: 4, value: "work out" }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const [checkedItems, setCheckedItems] = React.useState(
new Array(todos.length).fill(false)
);
const checkHandler = (idx) => {
checkedItems[idx] = !checkedItems[idx];
setItems([...items.filter((item) => item.id !== idx)]);
setCheckedItems([...checkedItems]);
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
</div>
);
}
Simply remove this line:
setItems([...items.filter((item) => !checkedItems[item.id])]);
that causes the list items to be filtered.

How to reset state with event.target.value and make it persist on multiple <li>

I am trying to grab the user input on key pressed and pass it to that list above next. I feel like there must be a way to reset the state and make it persist, but I just can't figure it out? How can I understand this?
import { useState, useEffect, useRef, useMemo } from 'react';
import '../sign.css';
const VALUES = [
{ id: 1, label: "name", text: "Hi, What is your Name?", placeholder: "Enter your full name" },
{ id: 2, label: "uname", text: "What shall we call you?", placeholder: "Enter a username" },
{ id: 3, label: "email", text: "Enter you email", placeholder: "Email" },
{ id: 4, label: "password", text: "Choose a password", placeholder: "make sure you dont forget" },
{ id: 5, label: "signup", text: "sign up", placeholder: ""},
];
export default function SignUp() {
const [show, setShow] = useState(VALUES)
const [currentIndex, setCurrentIndex] = useState(0);
const [details, setDetails] = useState('');
useEffect(() => {
}, [show]);
const onKeyPressed = (ev, id) => {
if (ev.charCode === 13) {
ev.preventDefault();
const nextRender = currentIndex + 1;
if (nextRender < show.length) {
setCurrentIndex(nextRender);
setDetails(ev.target.value);
} else {
//todo
}
}
}
const displayItem = useMemo(() => show[currentIndex], [show, currentIndex]);
return (
<div className="container" id="container">
<div className="navigation">
<ol>
<li>{this should display their name}</li>
<li>{this should display their username}</li>
<li>{this should display their email}</li>
</ol>
</div>
<form id="sign-form" className="sign-form">
<ol className="questions">
{
<li onKeyPress={(KeyboardEvent) => onKeyPressed(KeyboardEvent, displayItem.id)} key={displayItem.id} >
<span><label htmlFor={displayItem.label}>{displayItem.text}</label></span>
<input id={displayItem.id} name={displayItem.label} type="text" placeholder={displayItem.placeholder} autoFocus/>
</li>
};
</ol>
</form>
</div>
)
Right, I think I know what you mean now. I've run the code in CodeSandbox and it makes sense. You want to do like a stepper for your registration where you ask a single question at a time.
Storing values in an object would still be a preferable way of doing this. But you need to get the label for the value and append it to the existing object. You can build your object with data when you go through your stepper.
Here is a working solution. Hopefully that's what you were looking for: https://codesandbox.io/s/unruffled-pasteur-76xux4?file=/src/App.js
I modified the onKeyPressed to grab the label from VALUES array based on the index we are currently on. Then that label is used as a key inside of the object where the value is the value from the event handler
const onKeyPressed = (ev, id) => {
if (ev.charCode === 13) {
ev.preventDefault();
const label = show[currentIndex].label; // grab the label based on the current index
const nextRender = currentIndex + 1;
if (nextRender < show.length) {
setCurrentIndex(nextRender);
setDetails({ ...details, [label]: ev.target.value }); // add the value to the details object where key is the label
} else {
//todo
}
}
};
I let you the code that it works like I think that you want
import { useState, useEffect, useRef, useMemo } from 'react';
const VALUES = [
{ id: 1, label: "name", text: "Hi, What is your Name?", placeholder: "Enter your full name" },
{ id: 2, label: "uname", text: "What shall we call you?", placeholder: "Enter a username" },
{ id: 3, label: "email", text: "Enter you email", placeholder: "Email" },
{ id: 4, label: "password", text: "Choose a password", placeholder: "make sure you dont forget" },
{ id: 5, label: "signup", text: "sign up", placeholder: ""},
];
export default function SignUp() {
const [show, setShow] = useState(VALUES)
const [currentIndex, setCurrentIndex] = useState(0);
const [details, setDetails] = useState({});
useEffect(() => {
}, [show]);
const onKeyPressed = ( ev, id ) => {
if (ev.charCode === 13) {
ev.preventDefault();
const nextRender = currentIndex + 1;
if (nextRender < show.length) {
setCurrentIndex(nextRender);
const label = VALUES[currentIndex].label;
details[label] = ev.target.value;
setDetails(details);
} else {
//todo
}
}
}
const displayItem = useMemo(() => show[currentIndex], [show, currentIndex]);
return (
<div className="container" id="container">
<div className="navigation">
<ol>
{Object.keys(details).map((key) => (
<li><a href="#" dataref={key}>{key}: {details[key]}</a></li>
))}
</ol>
</div>
<form id="sign-form" className="sign-form">
<ol className="questions">
<li onKeyPress={( KeyboardEvent ) => onKeyPressed(KeyboardEvent, displayItem.id)} key={displayItem.id}>
<span><label htmlFor={displayItem.label}>{displayItem.text}</label></span>
<input id={displayItem.id} name={displayItem.label} type="text" placeholder={displayItem.placeholder}
autoFocus/>
</li>
</ol>
</form>
</div>
)
}

Toggle issue in React Js using Hooks

I am trying to implement toggle functionality, by using this functionality user can select desired single preference, and also the user can select all preferences by using the "Select All" button. I have implemented the code that is supporting a single selection I want to make select all functionality.
This is how i am handling toggle
const toggleItem = useCallback((isToggled, value) => {
if (isToggled) {
setToggledItems((prevState) => [...prevState, value]);
} else {
setToggledItems((prevState) => [...prevState.filter((item) => item !== value)]);
}
}, []);
const [toggledItems, setToggledItems] = useState([]);
var eventsnfo = [
{
icon: '',
title: 'select All',
subTitle: '',
},
{
icon: 'event1',
title: 'event1',
subTitle: 'event1desc',
},
{
icon: 'event2',
title: 'event2',
subTitle: 'event2desc',
},
{
icon: 'event3',
title: 'event3',
subTitle: 'event3desc',
},
{
icon: 'event4',
title: 'event4',
subTitle: 'event4desc',
},
];
this is how i am loading all toggle sections
<div>
{eventsnfo?.map(({ icon, title, subTitle }, index) => {
return (
<>
<div key={index} className='events__firstSection'>
<div className='events__agreeToAllContainer'>
{icon && (
<Icon name={icon} className='events__noticeIcon' isForceDarkMode />
)}
<div className={icon ? 'events__text' : 'events__text events__leftAlign '}>
{title}
</div>
</div>
<Toggle
containerClass='events__toggle'
checked={toggledItems.includes(title)}
onToggle={(isToggled) => toggleItem(isToggled, title)}
/>
</div>
{subTitle && <div className='events__description'>{subTitle}</div>}
<div className={index !== eventsnfo.length - 1 && 'events__divider'}></div>
</>
);
})}
</div>;
I think you can toggle all by changing your toggleItem function
const toggleItem = (isToggled, value) => {
let items = [...toggledItems];
if (isToggled) {
items =
value === "select All"
? eventsnfo?.map((events) => events.title)
: [...items, value];
if (items?.length === eventsnfo?.length - 1) {
items.push("select All");
}
} else {
items =
value === "select All"
? []
: [...items.filter((item) => item !== value && item !== "select All")];
}
setToggledItems(items);
};
Working Demo

Issue setting up a check filter for my web app

So I'm trying to set up a checkbox filter for a crime app my team and I are making but I am running into an issue where after clicking the checkbox my map function stops working. However, everything renders fine before you click on a checkbox and the state does seem to change via console log.
const [filter, setFilter] = useState([{id:1, value: "Rape", isChecked: false},
{id:2, value: "Assault", isChecked: false},
{id:3, value: "Burglary", isChecked: false},
{id:4, value: "Arson", isChecked: false},
{id:5, value: "Robbery", isChecked: false}])
const handleAllChecks = (event) => {
filter.forEach(filters => filters.isChecked = event.target.checked)
setFilter({filter:filter})
console.log("setFilter", filter);
}
const handleChecks = (event) => {
console.log("event", filter);
filter.forEach(filters => {
if(filters.value === event.target.value)
filters.isChecked = event.target.checked
})
setFilter({filter:filter})
}
return (
<div>
<input type="checkbox" onClick={handleAllChecks} value="checkedall" /> Check / Uncheck All
<ul>
{
filter.map((filters) => {
console.log(filters);
return(<CheckBox handleChecks={handleChecks} {...filters} />)
})
}
</ul>
</div>
)
}
This is the component I'm Mapping
console.log(props.handleChecks);
return (
<li>
<input key={props.id} onClick={props.handleChecks} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
</li>
)
}

Resources