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.
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>
))}
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
Hello I have a single list component which render some <Card> components that has a prop isSelected . Because alot of things happens when a <Card> Component has isSelected === true I added the state on the <List> component, and I want when someone clicks a card to check:
1) If there are no previously selected items ( state===null to add that item's id to state )
2) If someone clicks the same item or another item while there is already an item selected in state, to just unselected the active item.
import {Card} from "./Card";
import cloneDeep from 'lodash/cloneDeep';
const List = () => {
const [selectedCard, setSelectedCard] = useState(null);
const onCardClick = id => {
console.debug(selectedCard, id)
const newSelectedCard = cloneDeep(selectedCard);
// if he clicks another item while an item is active
// or if he clicks the same item while active
// should just make it inactive
if (newSelectedCard !== null || newSelectedCard === id) {
setSelectedCard(null)
} else {
setSelectedCard(id)
}
console.debug(selectedCard, id)
}
return (
<ul className="card-list">
{cardData.map(card => (
<Card
onClick={() => onCardClick(card.id)}
key={card.id}
isSelected={selectedCard === card.id}
{...card}
/>
))}
</ul>
)
}
export const CardList = () => (
<List/>
);
The issue is that the 2 console.debugs print the same values which means that the state doesnt update imediately and Im experiencing some strange behaviours here and there. Am I missing something here?
Basically you need to follow 3 condition as below
if(newSelectedCard === null){
setSelectedCard(id)
}
else if(newSelectedCard === id){
setSelectedCard(null);
}
else{
setSelectedCard(id)
}
Here is the Complete example:
import cloneDeep from 'lodash/cloneDeep';
import React, {useState} from "react";
const List = () => {
const [cardData, setCardData] = useState([
{id: 1, title: 'First Card'},
{id: 2, title: 'Second Card'},
{id: 3, title: 'Third Card'},
{id: 4, title: 'Fourth Card'},
]);
const [selectedCard, setSelectedCard] = useState(null);
const onCardClick = id => {
console.log(selectedCard, id);
const newSelectedCard = cloneDeep(selectedCard);
// if he clicks another item while an item is active
// or if he clicks the same item while active
// should just make it inactive
if(newSelectedCard === null){
setSelectedCard(id)
}
else if(newSelectedCard === id){
setSelectedCard(null);
}
else{
setSelectedCard(id)
}
console.log(selectedCard, id)
};
return (
<ul className="card-list">
{cardData.map(card => (
<Card
onClick={() => onCardClick(card.id)}
key={card.id}
isSelected={selectedCard === card.id}
{...card}
/>
))}
</ul>
)
};
export const CardList = () => (
<List/>
);
const Card = (props) => {
const backColor = props.isSelected? '#F9740E' : '#3FB566';
return (
<div onClick={() => props.onClick()}>
<div style={{backgroundColor: backColor, border: '1px solid darkgreen', color: 'white', padding: 10, marginBottom: 10}}>
<h3>{props.id}</h3>
<h4>{props.title}</h4>
</div>
</div>
);
};
Update
Here is Code SandBox
Not sure why you need to use cloneDeep.
const onCardClick = id => {
if (selectedCard === id) {
setSelectedCard(null);
} else {
setSelectedCard(id);
}
}
Updated
I use material ui next, Table component, and Table is stateless component, and also I have constructor component EnhancedTable. Also in Table component when I go map through data, I need to check if selected props(array) has the same item as on data array .
How should I check const isSelected = selected.includes(item) when I map through data array. Also when I click I catch error that selected.includes is not a function
unselectSelected={() => onSelect(selected =>{
debugger
console.log(selected)})}
Table component
let defaultCellRenderer = ({item, key}) =>
item[key]
const Table = props => {
const {data, columns, children, selectable, order, selected, onSelect, onDelete, onSearch, onDuplicate, onSort, search, onUnselectAll} = props
return (
<div>
{selectable &&
<EnhancedTableToolbar
numSelected={selected.length}
handleSearch={() => onSearch(data)}
value={search}
// unselectSelected={() => onUnselectAll(selected)}
unselectSelected={() => onSelect(selected =>{
debugger
console.log(selected)})}
deleteSelected={() => onDelete(selected)}
duplicateSelected={() => onDuplicate(selected)}
/> }
<MuiTable >
{selectable
? <EnhancedTableHead
columns={columns}
numSelected={selected.length}
order={order}
// onSelectAllClick={() => onSelect(Object.keys(new Array(selected.length).fill(0)))}
onSelectAllClick={() => onSelect(
console.log('click')
)}
onRequestSort={property => event => onSort(event, property)}
/>
: <TableHead>
<TableRow >
{columns.map(({label}) =>
<TableCell>
{label}
</TableCell>)}
</TableRow>
</TableHead>
}
<TableBody>
{data.map((item, index) => {
// debugger
const isSelected = selected.includes(item)
debugger
return (
selectable
? <TableRow
hover
onClick={() => onSelect(isSelected
? selected.filter(x => x != item)
: [...selected, item])}
role="checkbox"
aria-checked={isSelected}
tabIndex="-1"
key={index}
selected={isSelected}
>
<TableCell checkbox>
<Checkbox checked={isSelected}/>
</TableCell>
{columns.map(({key, cellRenderer, numeric}) =>
<TableCell key={key} numeric={numeric}>
{(cellRenderer || defaultCellRenderer)({item, key})}
</TableCell>)}
</TableRow>
: <TableRow hover>
{columns.map(({key, cellRenderer, numeric}) =>
<TableCell numeric={numeric}>
{(cellRenderer || defaultCellRenderer)({item, key})}
</TableCell>)}
</TableRow> )
})}
</TableBody>
</MuiTable>
</div>
)
}
EnchancedTable
class EnhancedTable extends Component {
state = {
selected: [],
data,
order: {
direction: 'asc',
by: 'deviceID',
},
search: '',
}
handleRequestSort = (event, property) => {
const orderBy = property
let order = 'desc'
if (this.state.order.by === property && this.state.order.direction === 'desc') {
order = 'asc'
}
const data = this.state.data.sort(
(a, b) => order === 'desc' ? b[orderBy] > a[orderBy] : a[orderBy] > b[orderBy],
)
this.setState({ data, order })
}
deleteSelected = () => {
const {data, selected} = this.state
this.setState({data: data.filter(item => !selected.includes(item)), selected: []})
}
handleSearch = event => {
const {data} = this.state
let filteredDatas = []
filteredDatas = data.filter(e => {
let mathedItems = Object.values(e)
let returnedItems
mathedItems.forEach(e => {
const regex = new RegExp(event.target.value, 'gi')
if (typeof e == 'string')
returnedItems = e.match(regex)
})
return returnedItems
})
this.setState({filterData: filteredDatas, search: event.target.value})
}
unselectSelected = () => {
this.setState({selected: []})
}
duplicate = () => {
const {data, selected} = this.state
this.setState({
// data: data.filter((item, index) => selected.includes(index)).reduce((p, c) => [...p, {...data[index]}], data),
data : [...data, ...selected],
selected: [],
})
}
handleSelectChange = selected => {
this.setState({selected})
}
render = () => {
const {selected, data, search, order} = this.state
return (
<Paper>
<Table
data={data}
selectable
columns={columns}
order={order}
search={search}
selected={selected}
onSelect={this.handleSelectChange}
onDelete= {this.deleteSelected}
onSort={this.handleRequestSort}
onDuplicate={this.duplicate}
onSearch={this.handleSearch}
// test unselect
onUnselectAll = {this.unselectSelected}
/>
</Paper>)
}
}
It seems to me that you've got several things wrong ( and a few things that are maybe just strange) here. Because I don't know the structure of data in your example I can't tell you if your check here for const isSelected = selected.includes(item) is working properly or not. If an item in your data array is a single value this will work. For example if you had:
const data = [ 1,2,3,4 ]
And
const selected = [1,2]
You could do the check the way you are currently doing and it would work. const isSelected = selected.includes(item)
But if your data is for example an object array like:
const data = [ {id: 1},{id: 2},{id: 3} ]
And
const selected = [{id:1}]
Then you would need to check the id value like this:
const isSelected = selected.some((i) => i.id === item.id )
And also it looks like you are not setting selected to a new value when you filter it in your onClick method which is probably why you're getting the selected.includes is not a function error. You should be doing this instead.
onClick={() => onSelect(isSelected ? selected = selected.filter(x => x != item) : [...selected, item])}
I'm not sure why you don't just have a your onSelect method handle both the select and unselect of a row. Something like:
onClick={() => this.onSelect()}
And then outside your render method:
onSelect() {
isSelected ? selected = selected.filter(x => x != item) : [...selected, item]
}
Hope that helps.