I currently have an array of objects. Each array of objects contains a key of checked with a value of type boolean. I am attempting to loop through the array when a user selects a certain checkbox and updating that objects checked value to either true or false. The issue I am having is spreading the updated object back into the array without creating duplicates. My code is as follows:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [arr, setArr] = useState([
{ id: 1, checked: false, name: "Person 1" },
{ id: 2, checked: true, name: "Person 2" }
]);
const updateCheck = (id) => {
const newArr = [...arr];
const object = newArr.find((r) => r.id === id);
const updatedObject = { ...object, checked: !object.checked };
console.log(updatedObject);
};
return (
<div className="App">
{arr.map((r) => {
return (
<>
<label>{r.name}</label>
<input
type="checkbox"
checked={r.checked}
onClick={() => updateCheck(r.id)}
/>
<br />
</>
);
})}
</div>
);
}
The desired effect I would like to achieve is that if Person 1's checkbox gets clicked I update their checked value to the opposite value. So Person 1 would have a checked value of true after their checkbox was clicked.
attached is a code sandbox https://codesandbox.io/s/elegant-haibt-hycmy?file=/src/App.js
Map the array state to create a new array state. If the item being iterated over has an ID that matches, return the updated object from the callback, otherwise return the existing item.
const updateCheck = (id) => setArr(
arr.map(item => (
item.id !== id ? item : { ...item, checked: !item.checked }
))
);
Related
I am working in React and using the filter array to compare the value of the returned array to the original array. However the filtered array is not returned. Here is the code I have:
const removeFromCart = id => {
const filteredCart = cart.filter(product => product.id !== id)
setCart(filteredCart)
If I use console.log on the cart variable set by setCart the new filtered array is shown. If I use console.log on filteredCart an empty array is shown. The filtered array also shows if !== is changed to ==.
When searching for a product by id to update a property an empty array is returned. Here is the code:
const addToCart = (id, name, price) => {
let inCart = cart.filter(product => product.id === id)
...
}
I know the filter function returns elements that pass the test so for the second code block, the object with the same id value should be returned instead of an empty array.
If the ids in the object are integers you will need to coerce the string id you pass to the function to an integer.
If you want to log the state after it's been updated you'll need to use useEffect and pass in cart as a dependency because enqueued state functions are processed asynchronously.
const { useEffect, useState } = React;
function Example({ data }) {
const [ cart, setCart ] = useState(data);
function removeFromCart(e) {
const { id } = e.target.dataset;
const filteredCart = cart.filter(product => product.id !== +id);
console.log(filteredCart)
setCart(filteredCart);
}
useEffect(() => {
console.log(JSON.stringify(cart));
}, [cart]);
return (
<div>
{cart.map(obj => {
return (
<div>
<div>{obj.name}
<span
className="remove"
data-id={obj.id}
onClick={removeFromCart}
>[Remove]
</span>
</div>
</div>
);
})}
</div>
);
};
const data = [
{ id: 1, name: 'Cheese' },
{ id: 2, name: 'Bread' },
{ id: 3, name: 'Jam' }
];
ReactDOM.render(
<Example data={data} />,
document.getElementById("react")
);
.remove { cursor: pointer; }
.remove:hover { font-weight: bold; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I have a list of users and near each user is a checkbox and a input number.
I want to save all data in the process of changing the data from input and checkbox:
import React, { useState } from "react";
import "./styles.css";
import { InputNumber, Checkbox } from "antd";
export default function App() {
const [user, setUser] = useState([
{
id: "",
nr: "",
checked: false
}
]);
function onChangeNr(id, value) {
console.log("changed", value, id);
setUser([
...user,
{
id: id,
nr: value,
checked: false
}
]);
}
console.log(user);
function onChangeCheck(e, id) {
console.log(`checked = ${e.target.checked}, ${id}`);
setUser([
...user,
{
id: id,
nr: "",
checked: e.target.checked
}
]);
}
return (
<div className="App">
<ul>
{[0, 1, 2, 3, 4].map((i) => {
return (
<li>
<p>User {i}</p>
<InputNumber
min={1}
max={10}
onChange={(id) => onChangeNr(i, id)}
/>
<Checkbox onChange={(check) => onChangeCheck(check, i)}>
Checkbox
</Checkbox>
</li>
);
})}
</ul>
</div>
);
}
Now i dont get the disserved result, but i want to get an array with all objects (unique objects). So each time when user change a data, the last value of the same id should override the previous value and at the final to get something like:
[
{id: 1, nr: 1, checked: true},
{id: 4, nr: 33, checked: true}
]
How to get the above result?
demo: https://codesandbox.io/s/elastic-nobel-79is1?file=/src/App.js:0-1165
To override the old state you can't use spread with an array => That will just combine the two arrays into one.
You either need to implement a search that will loop over all of the objects that you have inside the array and find the one that you need to update :
// loop over all entries
for(const i = 0; i < array.length; i++){
// is not the correct id, so not the correct obejct
if(array[i].id !== id) continue;
// found the correct object
array[i] = {id: i, ...};
}
// update state
setState(array);
But that would make js search every element once someone clicks the up / down arrows, so I would recommend that you put the state of the inputs into the array by their indexies:
// we already know the location of the state for the input because we put it into the same index as the key of the input!
array[i] = {id: i, ...}
setState(array)
Expample CodeSandbox
const reducer = (state, { type, i, checked, number }) => {
switch (type) {
case "setValues":
// reducer needs a new copy of the state
const newState = [...state];
newState[i] = {
id: i,
checked: checked ? checked : state[i]?.checked,
number: number ? number : state[i]?.number
};
console.log(newState);
return newState;
default:
break;
}
};
const [state, dispatch] = useReducer(reducer, initState);
return(
<InputNumber
min={1}
max={10}
onChange={(number) => {
dispatch({ type: "setValues", i, number });
}}
/>)
export default function Todos() {
const [todoState, updateStateTodo] = useState([
{ id: 1, title: "I am a todo object", completed: false },
{ id: 2, title: "I am a todo object 2", completed: false },
{ id: 3, title: "I am a todo object 3", completed: false },
{ id: 4, title: "I am a todo object 4", completed: true },
{ id: 5, title: "I am a todo object 5", completed: false },
]);
function handleChange(e) {
const newArray = todoState.filter((todo) => todo.id === e.target.value);
console.log(newArray);
console.log(e.target.value);
}
const todoItemArray = todoState.map((todo) => {
return (
<TodoItem title={todo.title} key={todo.id} completed={todo.completed} handleChange={handleChange} id={todo.id} />
);
});
return <div>{todoItemArray}</div>;
}
I have 2 main components joined. A todo item, which renders a specific item. And a todos component which renders them all in a list. Im using function based components. My state is stored with a hook in my todos component and my todo items are getting their info from props which are defined in todos from the state. Each "todo" in the state has a ID, title, completed. Im trying to figure out a way to make handleChange() work and make a new array for now with just the selected checkbox and that todos id. I dont know how to compare todo.id to the ID of the actual todo you clicked. e.target.value shows up as a number in the console of the one you clicked but when i filter() it doesnt work. (todo.id in filter() is working , i tested )
You want to pass in a separate handler callback to each TodoItem
const handleChange = (todo) => (e) => {
let newArray = todoState.filter(element => element.id === todo.id)
console.log(newArray)
}
const todoItemArray = todoState.map((todo) => {
return (
<TodoItem title={todo.title} key={todo.id} completed={todo.completed} handleChange={handleChange(todo)} id={todo.id} />
);
});
Here i have three filters on selection of which i need to filter data in a table.
I am using if else statement to check and filter the data , hence i want to modify the code in some modular way to achieve the same can any one suggest me , should i go with switch case ?
if (mapFilter === 'Mapped') {
if (listFilter) {
const result = fullData.filter(
data =>
data.partner_mapping_classification.length > 0 &&
data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification.length > 0,
);
setFinalData(result);
}
} else if (mapFilter === 'Not Mapped') {
if (listFilter) {
const result = fullData.filter(
data =>
data.partner_mapping_classification === '' &&
data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification === '',
);
setFinalData(result);
}
} else if (mapFilter === 'All') {
if (listFilter) {
const result = fullData.filter(
data => data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification.length > 0,
);
setFinalData(result);
}
} else if (mapFilter === '' && listFilter !== '') {
const result = fullData.filter(
data => data.account === listFilter,
);
setFinalData(result);
} else if (mapFilter === '' && listFilter === '') {
setFinalData([]);
} else {
setFinalData([]);
}
};
Easy to scale method (followed by live-demo)
Using switch statements or multiple chained if( statements (or, even, multiple conditions within same if( statement) doesn't seem to be a good idea, as scaling and maintaining such code will become way too difficult.
As the opposite to above mentioned hardcoding techniques, I would suggest to have an object within your table component's state that will bind object properties (you wish your table entries to get filtered by) to keywords (attached to your inputs).
Assuming (based on your screenshot) you use MaterialUI for styling your components, following example would demonstrate above approach:
const { useState } = React,
{ render } = ReactDOM,
{ Container, TextField, TableContainer, Table, TableHead, TableBody, TableRow, TableCell } = MaterialUI,
rootNode = document.getElementById('root')
const sampleData = [
{id: 0, name: 'apple', category: 'fruit', color: 'green'},
{id: 1, name: 'pear', category: 'fruit', color: 'green'},
{id: 2, name: 'banana', category: 'fruit', color: 'yellow'},
{id: 3, name: 'carrot', category: 'vegie', color: 'red'},
{id: 4, name: 'strawberry', category: 'berry', color: 'red'}
],
sampleColumns = [
{id: 0, property: 'name', columnLabel: 'Item Name'},
{id: 1, property: 'category', columnLabel: 'Category'},
{id: 2, property: 'color', columnLabel: 'Item Color'}
]
const MyFilter = ({filterProperties, onFilter}) => (
<Container>
{
filterProperties.map(({property,id}) => (
<TextField
key={id}
label={property}
name={property}
onKeyUp={onFilter}
/>
))
}
</Container>
)
const MyTable = ({tableData, tableColumns}) => (
<TableContainer>
<Table>
<TableHead>
<TableRow>
{
tableColumns.map(({id, columnLabel}) => (
<TableCell key={id}>
{columnLabel}
</TableCell>
))
}
</TableRow>
</TableHead>
<TableBody>
{
tableData.map(row => (
<TableRow key={row.id}>
{
tableColumns.map(({id, property}) => (
<TableCell key={id}>
{row[property]}
</TableCell>
))
}
</TableRow>
))
}
</TableBody>
</Table>
</TableContainer>
)
const App = () => {
const [state, setState] = useState({
data: sampleData,
columns: sampleColumns,
filterObj: sampleColumns.reduce((r,{property}) => (r[property]='', r), {})
}),
onFilterApply = ({target:{name,value}}) => {
const newFilterObj = {...state.filterObj, [name]: value}
setState({
...state,
filterObj: newFilterObj,
data: sampleData.filter(props =>
Object
.entries(newFilterObj)
.every(([key,val]) =>
!val.length ||
props[key].toLowerCase().includes(val.toLowerCase()))
)
})
}
return (
<Container>
<MyFilter
filterProperties={state.columns}
onFilter={onFilterApply}
/>
<MyTable
tableData={state.data}
tableColumns={state.columns}
/>
</Container>
)
}
render (
<App />,
rootNode
)
<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><script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script><div id="root"></div>
As Sudhanshu pointed out, you should create event listeners for all these select dropdowns and then update state based on that.
I created a small sample of how I would do it, but just be warned that this isn't tested and I just wrote it without actually running the code or anything. So it is buggy for sure in some regard.
const fullData = ['first', 'second', 'third'];
const BigFilter = () => {
const [activeFilters, setActiveFilters] = useState([]);
const [filteredValues, setFilteredValues] = useState([]);
const handleFilterChange = (event) => {
const { target } = event;
const isInFilter = activeFilters.some((element) => element.name === target.name);
if (!isInFilter) {
setActiveFilters((currentState) => {
return [...currentState, { name: target.name, value: target.value }];
});
} else {
setActiveFilters((currentState) => {
return [...currentState.filter((x) => x.name !== target.name), { name: target.name, value: target.value }];
});
}
};
useEffect(() => {
// Just set full data as filtered values if no filter is active
if (activeFilters.length === 0) {
setFilteredValues([...fullData]);
return;
};
let finalData = [...fullData];
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const listData = activeFilters.find((element) => (element.name = 'list'));
if (listData) {
// Do some filtering for first select/dropdown
const { value } = listData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const statusData = activeFilters.find((element) => (element.name = 'status'));
if (statusData) {
// Do some filtering for second select/dropdown
const { value } = statusData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const amountData = activeFilters.find((element) => (element.name = 'amount'));
if (amountData) {
// Do some filtering for third select/dropdown
const { value } = amountData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
setFilteredValues(finalData);
// You can go with multiple if statements to filter everything step by step
}, [activeFilters]);
return (
<>
<select name="list" onChange={handleFilterChange}>
<option>List Option 1</option>
</select>
<select name="status" onChange={handleFilterChange}>
<option>Status Option 1</option>
</select>
<select name="amount" onChange={handleFilterChange}>
<option>Amount Option 1</option>
</select>
<div>
{/* Render filtered values */}
{filteredValues.map((singleValue) => singleValue.name)}
</div>
</>
);
};
The basic idea here is that all your <select> elements react to the same event listener, making it easier to coordinate.
You got two basic arrays as state (activeFilters and filteredValues). When onChange handler is triggered, you check the name of the filter and check if that filter is already present in your activeFilters state. If it isn't, you add its name and value to that state. That's why I used name="some name" on each <select> in order to identify it somehow. In other case, if the filter is already present in that state, we remove it and just add its entry again with the new value. (This can probably be written way better, but it's just to give you an idea.)
Both of these cases set new state for active filters with setActiveFilter. Then we have the useEffect hook below which filters all the data based on active filters. As you can see it has that dependency array as a second argument and I added activeFilters variable to it so that every time activeFilters updates it will trigger all the logic in useEffect and it will change your filteredValues.
The logic in useEffect will go step by step and check if each filter is active and filter data for each of them if they are active step by step. If the first filter is active it will filter data that's needed and store it again in finalData and then it will go to the second if statement and if the filter for that is active it will perform another filter, but now on already filtered data. In the end, you should get data that passes through all active filters. I'm sure there's a better way of doing this, but it's a start.
Btw, usually I wouldn't do this
finalData = finalData.filter((x) => x.something > 0);
Re-assigning the same variable with filtered data from it, but I would say it's ok in this case since that finalData variable was created in that useEffect scope and it cannot be mutated from outside the scope. So it's easy to track what it is doing.
I'm sorry if this doesn't work, but it might guide you to your solution.
You can add a filter to the fullData array and provide the value of each of the dropdowns to the filter function
fullData.filter(element => {
return element.account == first && element.account == second && element.account == third;
});
You can also put in checks for the filters, like if the value is just '' then return false i.e return the whole array else return the filtered list
I am mapping multiple radio buttons (group) options and when the user click on radio buttons, would like to add selected values and uniqueIds to an array.
with my current code I can get the value that I am currently clicking on but can't add to array.
{result !== null && result.length > 0 ? (
<div>
{result.map(item => {
const label = item.question
const listOptions = item.options.map(item => {
return (
<Radio
name={item.uniqueId}
inline
value={item.uniqueId}
key={item.uniqueId}
className="radio-options"
checked={item.checked}
onChange={e => {
this.handleChange(label, uniqueId);
}}
>
{item.options}
</Radio>
);
});
return (
<div className="radio-options-overview">
<p>{label}</p>
{listOptions}
</div>
);
})}
handleChange = (label, uniqueId) => {
console.log(label, uniqueId);
let addToArray = [];
this.setState({ selectedItems: addToArray})
};
array would look something like this,
[
{ "label": "ageRange", "uniquId": 2 },
{ "label": "smoker", "uniquId": 1 },
{ "label": "exOption", "uniquId": 3 }
]
You are nearly there. #Clarity provided good solution.
if you wanting to replace exisiting value and replace it with new one
Try This
handleChange = (label, uniqueId) => {
const { selectedItems } = this.state
// Find items that already exists in array
const findExistingItem = selectedItems.find((item) => {
return item.uniqueId === uniqueId;
})
if(findExistingItem) {
// Remove found Item
selectedItems.splice(findExistingItem);
// set the state with new values/object
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
} else {
// if new Item is being added
this.setState(state => ({
selectedItems: [...state.selectedItems, {
label, uniqueId
}]
}))
}
};
You can do like this:
handleChange = (label, uniqueId) => {
this.setState(state => ({ selectedItems: [...state.selectedItems, uniqueId]}));
};
By using array spread and functional form of setState you make sure that you don't directly mutate the state and add the items to the latest state.
In case you'd want to add an object with a label: uniqueId pair, you could do like so:
handleChange = (label, uniqueId) => {
this.setState(state => ({
selectedItems: [...state.selectedItems, {
[label]: uniqueId
}]
}));
};
EDIT: If you want to overwrite the items with the same labels, the easiest would be to store them as an object and not an array, so the item with the same label would overwrite an existing one:
handleChange = (label, uniqueId) => {
this.setState(state => {
return { selectedItems: { ...state.selectedItems, [label]: uniqueId } };
});
};
Honestly, I don't understand, what are you trying to do, but if you need to add object (or something else) inside an array, you could use .push() method. For example:
let addToArray = [];
let test = {"label": "ageRange", "uniquId": 2};
addToArray.push(test);
console.log(addToArray); //[{label: "ageRange", uniquId: 2}]