I need to make that when selecting checkbox Select All, the rest of the checkboxes from the list are checked, and each checkbox can be selected separately. But when select one of the checkboxes does not checked the previous checkbox.
sandbox
const options = [ 'Selected Item 1', 'Selected Item 2', 'Selected Item 3'];
export default function App() {
const [selected, setSelected] = useState([]);
const isAllSelected =
options.length > 0 && selected.length === options.length;
const handleChange = (event) => {
const value = event.target.value;
console.log(value)
if (value === "all") {
setSelected(selected.length === options.length ? [] : options);
return;
}
setSelected(value);
};
const listItem = options.map((option) => {
return (
<div key={option}>
<Checkbox
value={option}
onChange={handleChange}
checked={selected.includes(option) } />
<span>{option}</span>
</div>
)
})
return (
<div style={{ display: 'flex', alignItems: 'center', margin: 10 }}>
<Checkbox
value='all'
onChange={handleChange}
checked={isAllSelected}
/>
<span> Select All</span>
{listItem}
</div>
);
}
When you click on checkboxes other than "all" checkbox, you are just setting your selected state to that value: setSelected(value);. There are 2 problems with this code - 1) In your case the selected state should always be an array, but you are changing that to a string. 2) you are disregarding any previous values that might be added to the selected state. You must figure out if the clicked checkbox was already in the selected state or not. if not, you should add it to the state. If it was then you should remove it from the state. Here is how you can do this:
const handleChange = (event) => {
const value = event.target.value;
console.log(value)
if (value === 'all') {
setSelected(selected.length === options.length ? [] : options);
return;
}
if (selected.indexOf(value) !== -1) { // if value already present
const newSelected = selected.filter((s) => s !== value );
setSelected(newSelected);
} else { // if value not present
setSelected([ ...selected, value ]);
}
}
You can make a data-structure like [{data:any, selected: boolean}, {data:any, selected: boolean}]
Then you loop through it and display them as
const [items, setItems] = useState<Asset[]>([]);
const [select, setSelect] = useState(false)
const handleChange = (index, checked) => {
if(select && !checked) setSelect(false)
const itemRef = [...items];
itemRef[index].selected = checked
setItems([...itemRef]);
}
const handleChangeAll = (checked) => {
const itemRef = [...items];
itemRef.forEach((_item, i) => {
itemRef[i].selected = checked
})
setSelect(checked)
setItems([...itemRef]);
}
------
All selection checkbox
<Checkbox
checked={select}
onChange={(_e, checked) => handleChangeAll(checked)}
/>
** items **
items.map((item, i) => (
<Checkbox
key={item.data.id}
checked={item.selected}
onChange={(_e, checked) => handleChange(index, checked)}
/>
))
Once it is done if you want to extract the selected data, you can just apply the filter method based on item.selected from items array,
Need to update selected options array instead of assigning the value directly inside handleChange method. So first check if selected list contains the checked option if it contains then remove it, otherwise add it to the selected list.
const handleChange = (event) => {
const value = event.target.value;
console.log(value);
if (value === "all") {
setSelected(selected.length === options.length ? [] : options);
return;
}
// added below code to update selected options
const list = [...selected];
const index = list.indexOf(value);
index === -1 ? list.push(value) : list.splice(index, 1);
setSelected(list);
};
Sandbox link here
Related
Im fetching data from an API and changing the value from the frontend to display it in a table. Im fetching a list of objects and storing it in a state and displaying the objects in the state in a html table. The table has a checkbox to display a boolean value. If the value is true, then defaultChecked is true in the checkbox. There's a checkbox in the table header to check or uncheck all items.
The following is the json object which i fetch.
{
completed: false
id: 1
title: "delectus aut autem"
userId: 1
}
If I checked the checkbox in the table header, I want to set completed to true in all 200 items.
The following is the code to set all items to either true or false.
const checkAllHandler = (e) => {
let val = e.target.checked;
console.log(val);
let allTodoList = [];
if (val === true) {
if (todo.length > 0) {
for (let index = 0; index < todo.length; index++) {
const newObject = {
userId: todo[index].userId,
id: todo[index].id,
title: todo[index].title,
completed: true,
};
allTodoList.push(newObject);
}
setTodo(allTodoList);
}
} else if (val === false) {
if (todo.length > 0) {
for (let index = 0; index < todo.length; index++) {
const newObject = {
userId: todo[index].userId,
id: todo[index].id,
title: todo[index].title,
completed: false,
};
allTodoList.push(newObject);
}
setTodo(allTodoList);
}
}
};
When I console the todo state, all the values are updated to either true or false but it doesnt show on the table.
The table has a filter function. If I filter a word and go back, then the values of entire table is changing. I want to display the change when the checkbox is clicked and not when search and go back. How to I do that?
The complete code of the component is below.
const DataTable = () => {
const { loading, items } = useSelector((state) => state.allData);
// console.log(items)
const dispatch = useDispatch();
// const history = useNavigate();
const [searchText, setsearchText] = useState("");
const [todo, setTodo] = useState(items);
console.log("todo");
console.log(todo);
const [isFetch, setisFetch] = useState(false);
const [checkedloading, setcheckedLoading] = useState(false);
const [isChecked, setisChecked] = useState(false);
console.log(isChecked);
useEffect(() => {
const setDataToState = () => {
if (loading === false) {
setTodo(items);
}
};
setDataToState();
}, [loading]);
useEffect(() => {
dispatch(getData());
// setTodo(items)
// console.log(items)
}, [dispatch]);
//etTodo(items)
const handleSearch = (event) => {
//let value = event.target.value.toLowerCase();
setsearchText(event.target.value);
};
const onChangeHandler = (e, item) => {
console.log(e.target.checked);
console.log(item);
if (e.target.checked === true) {
// const item = todo.filter(x=> x.id === id)
// console.log("added")
// console.log(item)
for (let index = 0; index < todo.length; index++) {
if (todo[index].id === item.id) {
console.log(todo[index]);
console.log("deleting");
todo.splice(index, 1);
console.log("deleted");
const newObject = {
userId: item.userId,
id: item.id,
title: item.title,
completed: true,
};
console.log("adding updated object");
todo.splice(index, 0, newObject);
console.log("added");
console.log(todo);
}
}
} else {
// const item = todo.filter(x=> x.id === id)
// console.log("removed")
// console.log(item)
for (let index = 0; index < todo.length; index++) {
if (todo[index].id === item.id) {
console.log(todo[index]);
console.log("deleting");
todo.splice(index, 1);
console.log("deleted");
const newObject = {
userId: item.userId,
id: item.id,
title: item.title,
completed: false,
};
console.log("adding updated object");
todo.splice(index, 0, newObject);
console.log("added");
console.log(todo);
}
}
}
};
const onSubmitHandler = () => {
localStorage.setItem("items", JSON.stringify(todo));
};
const getItem = () => {
const items = localStorage.getItem("items");
console.log("local storage items");
console.log(JSON.parse(items));
};
const checkAllHandler = async (e) => {
const { checked } = e.target;
console.log(checked);
setTodo((todos) =>
todos.map((todo) => ({
...todo,
completed: checked,
}))
);
};
return (
<>
{console.log("todo in render")}
{console.log(todo)}
<div className={styles.container}>
<div className={styles.top}>
<div className={styles.search_bar}>
<input
type="text"
onChange={(e) => handleSearch(e)}
placeholder="search by name"
/>
</div>
</div>
<div className={styles.btn_container}>
<button onClick={onSubmitHandler}>Submit</button>
</div>
<div className={styles.data_table_container}>
{checkedloading === false ? (
<>
<div className={styles.data_table}>
{loading || todo === null || todo === undefined ? (
<>
<p>Loading!!</p>
</>
) : (
<>
<table>
<tr>
<th>ID</th>
<th>userId</th>
<th>Title</th>
<th>
<>
Completed
<input type="checkbox" onChange={checkAllHandler} />
</>
</th>
</tr>
<tbody>
{todo
.filter((val) => {
if (searchText === "") {
return val;
} else if (
val.title.toLowerCase().includes(searchText)
) {
return val;
}
})
.map((item) => (
<>
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.userId}</td>
<td>{item.title}</td>
<td>
<input
type="checkbox"
defaultChecked={item.completed}
onClick={(e) => onChangeHandler(e, item)}
/>
</td>
</tr>
</>
))}
</tbody>
</table>
</>
)}
</div>
</>
) : (
<>Loading</>
)}
</div>
</div>
</>
);
};
CodeSandbox link: https://codesandbox.io/s/flamboyant-proskuriakova-60t19
I don't see any overt issues in this code, but it is quite verbose. Both logic branches are identical save for the completed boolean value assigned. When updating arrays in React it is common to use functional state updates to make the shallow copy of the previous state, not whatever state may be closed over in the callback scope.
Example:
const checkAllHandler = (e) => {
const { checked } = e.target;
console.log(checked);
setTodo(todos => todos.map(todo => ({
...todo,
completed: checked,
})));
};
If there's still further issue from here with updating the table then please add the rest of the component code and any other code you think is relevant.
Update
The reason none of the checkbox inputs are changing in the table is because you've used the defaultChecked prop, which makes these inputs fully uncontrolled inputs. They take the initial item.completed value when mounted and don't change from there other than if you interact with the checkbox.
If you want them to respond to changes/updates to the todo state then they should be converted to fully controlled inputs and use the checked prop.
<input
type="checkbox"
checked={item.completed}
onClick={(e) => onChangeHandler(e, item)}
/>
Update #2
The individual checkbox inputs were being mutated with Array.prototype.splice in onChangeHandler. .splice does an in-place mutation. Again a functional state update should be used to shallow copy the previous state and check for the matched todo object by id.
const onChangeHandler = (e, item) => {
const { checked } = e.target;
setTodo((todos) =>
todos.map((todo) =>
todo.id === item.id
? {
...todo,
completed: checked
}
: todo
)
);
};
I made a photo gallery with a filter for categories, but I want to make the active category button to have the active class
I display the buttons by mapping through the galleryData array
{
galleryData.map((item, index) => (
<button
key={index}
onClick={() => filterGalley(item.category)}
className="filter-button"
>
{item.category}
</button>
));
}
And onClick I filter the gallery items by category. The value is a string of category type
const filterGalley = (value) => {
if (value === "all") {
setGalleryItems(galleryData);
return;
}
const filteredData = galleryData.filter((item) => item.category === value);
console.log(value);
setGalleryItems(filteredData);
};
How can I pass the active class to the currently viewed category? onMount should be all and after that the one that's clicked.
Define a state for activeCategory and use it for active class:
const [activeCategory, setActiveCategory] = useState('all');
const filterGalley = (value) => {
setActiveCategory(value);
if (value === 'all') {
setGalleryItems(galleryData);
return;
}
const filteredData = galleryData.filter(
(item) => item.category === value
);
console.log(value);
setGalleryItems(filteredData);
};
And use it:
{galleryData.map((item, index) => (
<button
key={index}
onClick={() => filterGalley(item.category)}
className={"filter-button " + (activeCategory === item.category ? 'active' : '')}
>
{item.category}
</button>
))}
In this my project, I want the values checked to be displaying in front of the filtered applied but this is not working as expected. Anytime a box is checked, it display all the checked value and anytime a box is uncheck, it goes away automatically. Can someone please help me check what I'm doing wrong. codesandbox
My App.js code
import React from "react";
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
const [isselected, setisselected] = useState([]);
const OnChange = (e) => {
console.log(e.target.checked);
const ischecked = e.target.checked;
if (ischecked) {
setisselected([...isselected, e.target.value]);
} else {
const index = isselected.indexOf(e.target.value);
isselected.splice(index, 1);
setisselected(isselected);
}
console.log(isselected);
};
useEffect(() => {
console.log(isselected, "value selected");
}, [isselected]);
return (
<div className="App">
<span>
Filters applied:{" "}
{isselected.map((i) => (
<span>{i}</span>
))}
</span>
<div className="first-search">
<input
type="checkbox"
className="input-1"
value="Lastsevendays"
name="last_seven"
id="1"
onChange={OnChange}
/>
<label htmlFor="">Last 7 days</label>
</div>
<div className="first-search">
<input
type="checkbox"
className="input-1"
name="last24"
value="last_24"
id="2"
onChange={OnChange}
/>
<label htmlFor="">Last 24 hours</label>
</div>
</div>
);
}
Issue
You are mutating the isselected state when removing filters. Array.prototype.splice mutates the array in-place.
const OnChange = (e) => {
const ischecked = e.target.checked;
if (ischecked) {
setisselected([...isselected, e.target.value]);
} else {
const index = isselected.indexOf(e.target.value);
isselected.splice(index, 1); // <-- mutation!!
setisselected(isselected); // <-- same array reference
}
};
Solution
Use Array.prototype.filter to filter by index the isselected array and return a new array reference.
const OnChange = (e) => {
const ischecked = e.target.checked;
if (ischecked) {
setisselected([...isselected, e.target.value]);
} else {
const index = isselected.indexOf(e.target.value);
setisselected((isselected) => isselected.filter((_, i) => i !== index));
}
};
Since the isselected array is storing primitives, you can filter directly by the filter value as well.
setisselected((isselected) =>
isselected.filter((val) => val !== e.target.value)
);
what you're doing is that you are displaying the state with console.log() and it's the nature of console.log() that it shows the previuos value of the state,and also I did some changes in your Onchange function and it's working now try this and then let me know it's working or not:
const OnChange = (e) => {
console.log(e.target.checked);
const ischecked = e.target.checked;
if (ischecked) {
setisselected([...isselected, e.target.value]);
} else {
let temp = [isselected];
const index = temp.indexOf(e.target.value);
temp.splice(index, 1);
setisselected(temp);
}
console.log(isselected);
};
I've issues with the checkbox. I want to get the value and name field data in array format to do further processes.
Here's Checkbox:
<input type="checkbox" id="Honda" name="A1" value="Honda" onClick={CheckHandler} />
<label htmlFor="Honda" >Honda</label>
Now, I want to get the name and value field data in JSON or in an array
Like this:
{ name:"A1", value:"Honda" } //I want this.
So, I've coded like this:
import React, { Fragment, useState, useEffect } from "react";
export default function App() {
const [cars, setCars] = useState([]);
const CheckHandler = (e) => {
const value = e.target.value;
const name = e.target.name;
// setCars((prev) =>
// cars.includes(value)
// ? prev.filter((cur) => cur !== value)
// : [...prev, {[e.target.value]:`${e.target.name}`}]
// );
};
useEffect(() => {
console.log(cars);
}, [cars]);
return (
<Fragment>
<input type="checkbox" id="Honda" name="A1" value="Honda" onClick={CheckHandler}/>
<label htmlFor="Honda">Honda</label>
<br/>
<input type="checkbox" id="Mustang" name="A8" value="Mustang" onClick={CheckHandler}/>
<label htmlFor="Mustang">Mustang</label>
<br />
<input type="checkbox" id="Jeep" name="A12" value="Jeep" onClick={CheckHandler}/>
<label htmlFor="Jeep">Jeep</label>
<br />
</Fragment>
);
}
MY PROBLEM: whenever I Tick on checkbox It works fine, But when I unchecked its not returning sets from remaining items. IT's returning from all items. why ??
Any one Knows Answer ??
Sandbox https://codesandbox.io/s/late-water-piszn
Hi I fiddled a bit of your code and
const checkHandler = (e) => {
const value = e.target.value;
const name = e.target.name;
setCars((prev) =>
cars.find(ch => ch[value] === name)
? prev.filter((cur) => cur[value] !== name)
: [...prev, { [e.target.value]: `${e.target.name}` }]
);
};
update your method like this and it's working.
Here is the updated function I made for you answer
const CheckHandler = (e) => {
console.log(cars);
const value = e.target.value;
const name = e.target.name;
var found = false;
for (var i = 0; i < cars.length; i++) {
if (cars[i][value] === name) {
found = true;
break;
}
}
setCars((prev) =>
found
? prev.filter((cur) => cur[value] !== name)
: [...prev, { [value]: name }]
);
};
Here is the sandbox
I have a list of buttons and they are multi selectable. when I select the buttons I want to add , it will be added to the array perfectly and turned to blue and when I click on a already selected button it should be get removed from the array and turned to white but it doesn't. Below shows what I tried so far.
The first array (products) is to save the API data. Second one is to save the selected products.
const [products, setProducts] = useState([]);
const [selectedProducts, setselectedProducts] = useState<any>([]);
{products.length !== 0 ? (
products?.map(
(item: any, index) => (
<SButton
key={item.key}
label={item.value}
onClick={() => {
selectedProducts(item);
}}
isSelected={item.selected === "YES"}
/>
)
)
) : (
<p>No products</p>
)}
function selectedProducts(item:any){
if(selectedProducts.length !== 0){
selectedProducts.map((selecteditem:any)=>{
if(selecteditem.key == item.key ){
item.selected = "NO";
setselectedProducts(selectedProducts.filter((item: any )=> item.key !== selecteditem.key))
}else{
item.selected = "YES";
setselectedProducts([...selectedProducts, item]);
}
})
}else{
setselectedProducts([...selectedProducts, item]);
item.selected = "YES";
}
}
How about something like this?
const [products, setProducts] = useState([]);
const [selectedProduct, setSelectedProduct] = useState();
{(products.length > 0) ? (
<Fragment>
{products.map((item)=>{
const {key, value, selected } = item;
return (
<SButton
key={key}
label={value}
onClick={() => {
setSelectedProduct(item);
const newState = !selected;
products.forEach((p)=>{
if (p.key === item.key) p.selected = newState;
});
setProducts([...products]);
}}
isSelected={selected}
/>
);
})}
</Fragment>
): (
<p>No products</p>
)}
First, you are using selectedProducts both as function name and name of selected products state.
Second, you should not assign values to item. Use spread operator instead.
Also, you can access the previous state from setState instead of using state directly.
function removeFromSelectedProducts(item: any) {
// Set selected to 'NO' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'NO' } : product
)
)
// Remove product from selectedProducts
setSelectedProducts((prevSelectedProducts) =>
prevSelectedProducts.filter((product) => product.key !== item.key)
)
}
function addToSelectedProducts(item: any) {
// Set selected to 'YES' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'YES' } : product
)
)
// Add item to selectedProducts
setSelectedProducts((prevSelectedProducts) => [...prevSelectedProducts, { ...item, selected: 'YES'}])
}
function selectProduct(item: any) => {
if (selectedProducts.some((product) => product.key === item.key)) {
removeFromSelectedProducts(item)
} else {
addToSelectedProducts(item)
}
}
You can simplify this using useReducer hook instead of using separate functions for addition & removal of selected products.