Need to delete item from array using redux - javascript

so I have a reducer that is adding to array
create reducer :
export default (itemsList = [], action) => {
if (action.type === 'ADD_ITEM') {
return [...itemsList, action.payload]
}
return itemList
}
deleting reducer (99% that something is wrong here, but I have no idea what ):
export default (itemList = [], action) => {
if (action.type === 'DELETE_ITEM') {
return [...itemList, itemList.filter(item => item !== action.payload)]
}
return itemList
};
action/index.js:
export const addItemToList = item => {
return {
type: 'ADD_ITEM',
payload: selectedItem
}
};
export const deleteItemFromList = item => {
return{
type: 'DELETE_ITEM',
payload: selectedItem
}
};
let say I have
itemList = [ 'abc', 'xyz', 'qwe' ]
and I want to use deleteItem('xyz') to delete 'xyz' from itemList

While deleting you just need to return the filtered list and not use spread operator too.
export default (itemList = [], action) => {
if (action.type === 'DELETE_AUTHOR') {
return itemList.filter(item => item !== action.payload)
}
return listOfAuthorsSelected
};

Array.filter() returns a new array with given filter condition and not mutate the existing array. You don't have need to use ...itemList(spread operator). Here you are actually adding a sub array each time.
Here is a simple running example
var array1 = ["abc", "def" , "ghi"];
var array2 = [...array1, array1.filter((item) => {
return item !== "def"
})];
document.write(array2);
// correct way to filter
var array3 = ["abc", "def" , "ghi"];
var array4 = array3.filter((item) => {
return item !== "def"
});
document.write("<hr/>"+array4);

Related

Reducer add item and update item in array of objects without mutation

Hello created reducer and wondering is this right pattern.
I am trying to:
Add new item into array of objects ACTION_TYPE
Update item in array of objects ACTION_TYPE_EDIT, this one actually should set charity prop and update item in array charities
Wondering if i am on right path, i don't want to mutate, so here is my code.
export const reducerCharities = (
state: CharityReducerState,
action: CharitiesAction
| CharityEditAction
) => {
switch (action.type) {
case ACTION_TYPE_EDIT:
return {
...state,
charity: {
...state.charity,
...(action.charity || {}),
},
charities: [
...state.charities.map(item => item.id === action.charity?.id ? { ...item, ...action.charity } : item)
]
}
case ACTION_TYPE:
return {
...state,
charities: [
...state.charities || [],
...action.charities
]
}
default:
return state
}
}
So my question is will this part mutate state or is there better way to write this?
charities: [
...state.charities.map(item => item.id === action.charity?.id ? { ...item, ...action.charity } : item)
]
Is this right approach how to add new item into array
{
...state,
charities: [
...state.charities || [],
...action.charities
]
}
The easiest (and since 2019 officially recommended) way of doing this would be using the official Redux Toolkit package and the createSlice method.
Within that, you can just mutate and don't have to care about manual immutable updates - the package takes over for you.
The code could look like
const charitiesSlice = ({
name: 'charities',
initialStae: myInitialState,
reducers: {
edit(state, action: PayloadAction<Charity>){
const charity = state.charities.find(item => item.id === action.payload.id)
if (charity) {
Object.assign(charity, action.payload)
}
state.charity = action.payload
}
}
}
export const { edit } = charitiesSlice.actions
export const reducerCharities = charitiesSlice.reducer

Remove duplicates from inner array of array of objects

Here is my array. How can I remove duplicates in this type of structure? When I map over arr I get the values of each array nested in each object. And I want to filter the duplicated values.
current output: bye hello hello
The expected output should be: bye hello
[
{
arr: ['']
val: "string"
}
{
arr: ['bye', 'hello']
val: "string"
}
{
arr: ['hello']
val: "string"
}
]
myArray.map(item => item.arr.map((el, index) =>
<p key={index}>{el}</p>
))
I hope it will help you:
const filteredArray = useMemo(() => {
const used = []
return myArray.map(sub => {
return { ...sub, arr:sub.arr.map(el
=> {
if(used.includes(el) return null
used.push(el)
return el
}}
})
}, deps)
And then in JSX:
filteredArray.map(() => ...)
You could simply manage an array to filter what you want to display :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const data = [
{
arr: [''],
val: 'string',
},
{
arr: ['bye', 'hello'],
val: 'string',
},
{
arr: ['hello'],
val: 'string',
},
];
const buildData = () => {
const passedValues = [];
return data.map((item) => {
return item.arr.map((el) => {
if (!passedValues.includes(el)) {
passedValues.push(el);
return <div>{el}</div>;
}
});
});
};
return <div>{buildData()}</div>;
};
render(<App />, document.getElementById('root'));
Here is the repro on StackBlitz.
All of these answer are good...I think #vitaliyirtlach has the best as its the closest to React.
I'll just put it out there that you can also loop through myArray, remove the duplicates with Set and then place them in an array that you can loop over:
const myArray = [
{
arr: [''],
val: "string"
},
{
arr: ['bye', 'hello'],
val: "string"
},
{
arr: ['hello'],
val: "string"
}
]
const removeDupes = () => {
const newArr = []
myArray.map(item => item.arr.map((el, index) =>
newArr.push(el)
))
return [...new Set(newArr)]
}
const loopArray = removeDupes();
console.log(loopArray)// logs ["","bye","hello"]
loopArray.map((el, index) =>
<p key={index}>{el}</p>
))

How to update state with usestate in an array of objects?

I'm having some trouble with the React useState hook. I have a todolist with a checkbox button and I want to update the 'done' property to 'true' that has the same id as the id of the 'clicked' checkbox button. If I console.log my 'toggleDone' function it returns the right id. But I have no idea how I can update the right property.
The current state:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: false
},
]
})
const toggleDone = (id) => {
console.log(id);
}
return (
<div className="App">
<Todos todos={state.todos} toggleDone={toggleDone}/>
</div>
);
}
The updated state I want:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: true // if I checked this checkbox.
},
]
})
You can safely use javascript's array map functionality since that will not modify existing state, which react does not like, and it returns a new array. The process is to loop over the state's array and find the correct id. Update the done boolean. Then set state with the updated list.
const toggleDone = (id) => {
console.log(id);
// loop over the todos list and find the provided id.
let updatedList = state.todos.map(item =>
{
if (item.id == id){
return {...item, done: !item.done}; //gets everything that was already in item, and updates "done"
}
return item; // else return unmodified item
});
setState({todos: updatedList}); // set state to new object with updated list
}
Edit: updated the code to toggle item.done instead of setting it to true.
You need to use the spread operator like so:
const toggleDone = (id) => {
let newState = [...state];
newState[index].done = true;
setState(newState])
}
D. Smith's answer is great, but could be refactored to be made more declarative like so..
const toggleDone = (id) => {
console.log(id);
setState(state => {
// loop over the todos list and find the provided id.
return state.todos.map(item => {
//gets everything that was already in item, and updates "done"
//else returns unmodified item
return item.id === id ? {...item, done: !item.done} : item
})
}); // set state to new object with updated list
}
const toggleDone = (id) => {
console.log(id);
// copy old state
const newState = {...state, todos: [...state.todos]};
// change value
const matchingIndex = newState.todos.findIndex((item) => item.id == id);
if (matchingIndex !== -1) {
newState.todos[matchingIndex] = {
...newState.todos[matchingIndex],
done: !newState.todos[matchingIndex].done
}
}
// set new state
setState(newState);
}
Something similar to D. Smith's answer but a little more concise:
const toggleDone = (id) => {
setState(prevState => {
// Loop over your list
return prevState.map((item) => {
// Check for the item with the specified id and update it
return item.id === id ? {...item, done: !item.done} : item
})
})
}
All the great answers but I would do it like this
setState(prevState => {
...prevState,
todos: [...prevState.todos, newObj]
})
This will safely update the state safely. Also the data integrity will be kept. This will also solve the data consistency at the time of update.
if you want to do any condition do like this
setState(prevState => {
if(condition){
return {
...prevState,
todos: [...prevState.todos, newObj]
}
}else{
return prevState
}
})
I would create just the todos array using useState instead of another state, the key is creating a copy of the todos array, updating that, and setting it as the new array.
Here is a working example: https://codesandbox.io/s/competent-bogdan-kn22e?file=/src/App.js
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
title: "take out trash",
done: false
},
{
id: 2,
title: "wife to dinner",
done: false
},
{
id: 3,
title: "make react app",
done: false
}
]);
const toggleDone = (e, item) => {
const indexToUpdate = todos.findIndex((todo) => todo.id === item.id);
const updatedTodos = [...todos]; // creates a copy of the array
updatedTodos[indexToUpdate].done = !item.done;
setTodos(updatedTodos);
};

What is the problem in this redux action and reducer?

I know that redux trigger react component to re-render once state of that component get changed but this doesn't happen in my situation.
Action
const addToCart = (product, cartProducts) => {
let newCartProducts = [...cartProducts, product];
newCartProducts = newCartProducts.reduce((acc, cur) => {
let repeated = acc.find(p => p.id === cur.id);
if(repeated) repeated.quantity++;
else acc.push(cur);
return acc;
}, []);
return {
type: ADD_TO_CART,
payload: newCartProducts
}
}
Reducer:
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
return action.payload;
default:
return state;
}
}
The reducer returns a new state every time the action dispatched from the component but i need to close the cart and open it again to get the effect, redux does not update the product quantity simultaneously??
You are modifying the existing elements in the state.
Use
newCartProducts = newCartProducts.reduce((acc, cur) => {
let repeatedIndex = acc.findIndex(p => p.id === cur.id);
const repeated = acc[repeatedIndex];
if (repeated) {
acc[repeatedIndex] = {
...repeated,
quantity: repeated.quantity + 1
};
} else acc.push(cur);
return acc;
}, []);
You array is recreated each time, but the objects inside it are not. So when you modify their internals you need to notify that the specific object has changed.
Refactor logic to the reducer and set the quantity here:
const addToCart = product => {
return {
type: ADD_TO_CART,
payload: product,
};
};
//I assume state is an array of products on your cart
export default (state = [], action) => {
switch (action.type) {
case ADD_TO_CART:
const { id } = action.payload;
return state.map(p => p.id).includes(id)
? //product is already on card add quanity
state.map(p =>
p.id === id
? { ...p, quantity: p.quantity + 1 }
: p
)
: state.concat({ ...action.payload, quantity: 1 }); // add product
default:
return state;
}
};

Filtering an array by category name

I would like to filter an array of images based on their category property.
I am able to map and push the category property of all images into a new array and set the state to the new array.
However, I am stuck figuring out how to check for duplicates in the new array, and NOT push a new value if it's already exists.
interface Asset {
id: string
name: string
category: string
}
import * as React from "react"
interface MediaLibraryProps {
mediaLibrary: Asset[]
}
class MediaLibrary extends React.Component<MediaLibraryProps> {
state = {
categories: [],
}
categoryFilter = () => {
const categoryArray: any = []
this.props.mediaLibrary.filter(file => {
if (file.category !== categoryArray) {
categoryArray.push(file.category)
} else {
return
}
})
this.setState({
categories: categoryArray
})
console.log(this.state.categories)
}
render() {
const select = this.state.categories.map(category =>
<option key={category}>{category}</option>
)
return (
<div>
<select>
{ select }
</select>
<button onClick={this.categoryFilter}>LOG</button>
</div>
)
}
}
export default MediaLibrary
I'm expecting only unique names to be pushed to the categories array.
Actual results - everything is being pushed.
See Remove duplicate values from JS array
Example:
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
Quick answer for your question:
const categoryArray: any = []
this.props.mediaLibrary.filter(file => {
if (categoryArray.indexOf(file.category) < 0) {
categoryArray.push(file.category)
} else {
return
}
})
this.setState({
categories: categoryArray
})
console.log(this.state.categories)
Better approach:
The filter here is unnecessary. better approach is to use map.
const categoryArray: any = []
this.props.mediaLibrary.map(file => {
if (categoryArray.indexOf(file.category) < 0) {
categoryArray.push(file.category)
}
})
this.setState({
categories: categoryArray
})
console.log(this.state.categories)
It can be achieved with filter.As per question,
let filteredData = this.props.mediaLibrary.filter(file => file.category !== categoryArray);

Categories