How do I add elements in my array arr[] of redux state in reducer?
I am doing this-
import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {
arr:[]
}
export default function userState(state = initialUserState, action)
{
console.log(arr);
switch (action.type)
{
case ADD_ITEM:
return {
...state,
arr: state.arr.push([action.newItem])
}
default:
return state
}
}
Two different options to add item to an array without mutation
case ADD_ITEM :
return {
...state,
arr: [...state.arr, action.newItem]
}
OR
case ADD_ITEM :
return {
...state,
arr: state.arr.concat(action.newItem)
}
push does not return the array, but the length of it (docs), so what you are doing is replacing the array with its length, losing the only reference to it that you had. Try this:
import {ADD_ITEM} from '../Actions/UserActions'
const initialUserState = {
arr:[]
}
export default function userState(state = initialUserState, action){
console.log(arr);
switch (action.type){
case ADD_ITEM :
return {
...state,
arr:[...state.arr, action.newItem]
}
default:return state
}
}
If you need to insert into a specific position in the array, you can do this:
case ADD_ITEM :
return {
...state,
arr: [
...state.arr.slice(0, action.pos),
action.newItem,
...state.arr.slice(action.pos),
],
}
Since this question gets a lot of exposure:
If you are looking for the answer to this question, there is a good chance that you are following a very outdated Redux tutorial.
The official recommendation (since 2019) is to use the official Redux Toolkit to write modern Redux code.
Among other things, that will eliminate string action constants and generate action creators for you.
It will also employ methods that allow you to just write mutating logic in your Reducers created by createReducer or createSlice, so there is no need to write immutable code in Reducers in modern Redux in the first place.
Please follow the official Redux tutorials instead of third-party tutorials to always get the most up-to-date information on good Redux practices and will also show you how to use Redux Toolkit in different common scenarios.
For comparison, in modern Redux this would look like
const userSlice = createSlice({
name: "user",
initialState: {
arr:[]
},
reducers: {
// no ACTION_TYPES, this will internally create a type "user/addItem" that you will never use by hand. You will only see it in the devTools
addItem(state, action) {
// you can use mutable logic in createSlice reducers
state.arr.push(action.payload)
}
}
})
// autogenerated action creators
export const { addItem } = slice.actions;
// and export the final reducer
export default slice.reducer;
If you want to combine two arrays, one after another then you can use
//initial state
const initialState = {
array: [],
}
...
case ADD_ARRAY :
return {
...state,
array: [...state.array, ...action.newArr],
}
//if array = [1,2,3,4]
//and newArr = [5,6,7]
//then updated array will be -> [1,2,3,4,5,6,7]
...
This Spread operator (...) iterates array element and store inside the array [ ] or spreading element in the array, what you can simply do using "for loop" or with any other loop.
I have a sample
import * as types from '../../helpers/ActionTypes';
var initialState = {
changedValues: {}
};
const quickEdit = (state = initialState, action) => {
switch (action.type) {
case types.PRODUCT_QUICKEDIT:
{
const item = action.item;
const changedValues = {
...state.changedValues,
[item.id]: item,
};
return {
...state,
loading: true,
changedValues: changedValues,
};
}
default:
{
return state;
}
}
};
export default quickEdit;
The easiest solution to nested arrays is concat():
case ADD_ITEM:
state.array = state.array.concat(action.paylod)
return state
concat() spits out an updated array without mutating the state. Simply set the array to the output of concat() and return the state.
This worked for me
//Form side
const handleSubmit = (e) => {
e.preventDefault();
let Userdata = { ...userdata, id: uuidv4() };
dispatch(setData(Userdata));
};
//Reducer side
const initialState = {
data: [],
};
export const dataReducer = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.SET_DATA:
return { ...state, data: [...state.data, action.payload] };
default:
return state;
}
};
Related
I have a code sandbox to demo an issue I am having with Redux and React.
https://codesandbox.io/s/reactredux-3edy8
Basically this reducer does not work:
const initialState = {
byId: {}
};
function addReducer(state = initialState, action) {
switch (action.type) {
case "RECEIVE_DATA":
return {
...state,
byId: {
...state.byId,
[action.payload.id]: [
...state.byId[action.payload.id],
action.payload.data
]
}
};
default:
return state;
}
}
export default addReducer;
here is the action:
import { RECEIVE_DATA } from "./actionTypes";
export function action() {
return {
type: RECEIVE_DATA,
payload: {
id: Math.floor(Math.random() * 2),
data: Math.floor(Math.random() * 10000)
}
};
}
In my own app it throws an error like state.byId[action.payload.id] is not iterable which is true. But how do I push values to an array that does not yet exist because I do not know the id?
I thought the issue might be solved by changing the initialState or perhaps there is a function I am not aware of that allows me to either create an array or push to it.
Check if the array exists first. If it does, add a new element. If not, create a new array with one element (the new data from the action)
const initialState = {
byId: {}
};
function addReducer(state = initialState, action) {
switch (action.type) {
case "RECEIVE_DATA":
return {
...state,
byId: {
...state.byId,
[action.payload.id]: (
state.byId[action.payload.id] ? [
...state.byId[action.payload.id],
action.payload.data
] : [action.payload.data])
}
};
default:
return state;
}
}
export default addReducer;
You can read more about ternary statements here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
I am not sure but I think you need something like this
function addReducer(state = initialState, action) {
switch (action.type) {
case "RECEIVE_DATA":
return {
...state,
byId: {
...state.byId,
[action.payload.id]:
[action.payload.data]
}
};
default:
return state;
}
}
The answer to this problem lies in flattening the state. I am sure there are other ways to fix this problem, but I found this the cleanest.
Typically the byId property of state is created through flattening state per the guidelines in the docs
I take it one step further, making the byId property an Array instead of an Object. I move the id into the objects of the array like in this new sandbox: https://codesandbox.io/s/reactredux-zj1n4
I just have to remember the byId is now an array when I map the state to my components.
No functionality is lost and my byId field can now support multiple different ids if needed (e.g. if I had many to many relationships on the objects)
Here is the code:
in reducer
case "RECEIVE_DATA":
return {
...state,
byId: [...state.byId, action.payload.data]
};
new action:
export function action() {
return {
type: RECEIVE_DATA,
payload: {
data: {
id: Math.floor(Math.random() * 2),
value: Math.floor(Math.random() * 10000)
}
}
};
}
I understand that concat clones a new array of state.articles with action.payload so why is state needed as a first source. Is it because an object of state of array state.articles + action.payload is created?
import { ADD_ARTICLE } from "../constants/action-types";
const initialState = {
articles: []
};
function rootReducer(state = initialState, action) {
if (action.type === ADD_ARTICLE) {
return Object.assign({}, state, {
articles: state.articles.concat(action.payload)
});
}
return state;
const initialState = {
articles: []
};
function rootReducer(state = initialState, action) {
if (action.type === ADD_ARTICLE) {
return { ...state, articles: [...state.articles, action.payload] }
}
return state;
Your code can be shorten to above code. Do note that it's exactly equivalent to your code.
To answer your question, articles is only one of the properties, and state is an object(referring to initialState structure), and hence by having Object.assign will create an entire new set of state, which embracing immutablity.
Please do note that you are feel free to construct state into any data structure as you wished, as long as you're returning a new instance of the state, you're good to go. Usually we construct state as an object due to easier to scale horizontally
I am using redux with react and I am trying to append my array of objects gotten from my api to my redux state which is an array of objects
This is my reducer...
import { GET_BOOKS } from "../actions/types";
const initialState = {
books: [
0:{},
1:{},
]
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_BOOKS:
console.log(action.payload.results);
return { ...state };
default:
return state;
}
}
My api is returning
results : [
0: {somevalue},
1: {somevalue}
]
I dont know how to spread the values into a new array.
Simply assign the property and it will overwrite the old one.
export default function(state = initialState, action) {
switch (action.type) {
case GET_BOOKS:
console.log(action.payload.results);
// spread current state and inaddition to that set new books data
// which overwrites books property from old state
return { ...state, books : action.payload.results };
// spread --^^^^^^^^^^^^^^^^^^^^^^^^^^^---
default:
return state;
}
}
UPDATE : If you want to concatenate it with existing then do something like this.
export default function(state = initialState, action) {
switch (action.type) {
case GET_BOOKS:
console.log(action.payload.results);
return { ...state, books : [...state.books, ...action.payload.results] };
default:
return state;
}
}
FYI : The ...state part is for copying other state properties(assumes there exists other state values)
You need to concat current state and data coming from api
return { books: [...state.books, ...action.payload.results] };
Complete Code
import { GET_BOOKS } from "../actions/types";
const initialState = { books: [] };
export default (state: Object = initialState, action: Object) => {
switch (action.type) {
case GET_BOOKS:
return { books: [...state.books, ...action.payload.results] };
default:
return state;
}
};
I am using axios in the action creator and redux-promise middleware to return an array of objects to a map function in the render method of my component. When using the first return statement with ES6 spread, I get an array within an array. How would I properly iterate through this array? Would I use map in the reducer? The second return statement works. I am unclear as to why I wouldn't want just return the array by itself. Thanks!!
const INITIAL_STATE = [];
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case GET_TICKET:
return [action.payload.data, ...state];
return action.payload.data;
default:
return state;
}
}
There's two main ways to combine two arrays into one:
const newArray = [...firstArray, ...secondArray];
// or
const newArray = firstArray.concat(secondArray);
Either of those will work in your case, like:
return state.concat(action.payload.data);
you can follow this format
const INITIAL_STATE = { myData : [] };
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case GET_TICKET:
return {
...state,
myData: [...myData, ...action.payload.data]
};
default:
return state;
}
}
EDIT
Enhanced this answer to illustrate what the OP meant, taken from the accepted answer:
[...firstArray, ...secondArray]
I have an array of comments which I want to filter on based on an ID and change a specific key/value pair. My console log function returns the correct item in the array but I'm unsure of the correct syntax for taking the result of the .filter function and changing the 'liked' key from 'false' to 'true'. I am able to use the spread operator if it is suitable in this situation.
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function commentReducer(state = initialState.comments, action) {
switch (action.type) {
case types.ADD_COMMENT_LIKE_SUCCESS:
const content = Object.assign({}, state);
const likedComment = content.data.filter(comment => (
comment.id === action.commentId
));
console.log(likedComment[0]);
break;
default:
return state;
}
}
The comments object look like this:
"data":{
"data":[
{ id: '', comment: '', liked: false },
{ id: '', comment: '', liked: false },
{ id: '', comment: '', liked: false },
{ id: '', comment: '', liked: false }
],
"cursor":"CkUKEQoEZGF0ZRIJCNrwhcWhvdUCEixqFGRldn5kZXZlbG9wbWVudC0xMzAwchQLEgdDb21tZW50GICAgLSe_-cKDBgAIAE=",
"more":false,
"count":4
},
You are using the filter function with correct syntax, although your reducer should return all the comments with that particular comment with liked key value pair as true, this how you can go ahead using the map function for the same.
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function commentReducer(state = initialState.comments, action) {
switch (action.type) {
case types.ADD_COMMENT_LIKE_SUCCESS:
const content = Object.assign({}, state);
content.data = content.data.map(comment => {
const newObj = {...comment};
if(newObj.id === action.commentId)
{
newObj.likeKey = true;
}
return newObj;
});
console.log(content);
return content;
default:
return state;
}
}
Map functin will return all the comments you have till now, with that particular comment object changed. Hope this helps.
I would use find instead of filter since it always return only one item.
I'm not completely sure about how you splitted your state, but I can suggest to return like:
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function commentReducer(state = initialState.comments, action) {
switch (action.type) {
case types.ADD_COMMENT_LIKE_SUCCESS:
const otherComments = state.data;
const likedComment = state.data.find(comment => (
comment.id === action.commentId
));
const index = state.data.indexOf(likedComment);
return {
...state,
data: {
[
...otherComments.slice(0, index),
{ ...likedComment, liked: true }, //Override properties here
...otherComments.slice(index + 1)
]
}
};
default:
return state;
}
}
There's no need for copy content from state, since I'm not modifying state.
Basically I'm:
Copying state and overriding data key with ...
A copy of all comments and concatenating with my copy likedComment, but with overriden data