How to update nested objects state of array in redux? - arrays

I want to update state of an object in array of array with particular index from the action payload. I have this kind of state in reducers.
const INITIAL_STATE = {
tasks: {
taskArray: [
{
maintask: 'Main task 1',
subtask: [
{
photo_urls: [],
task_name: 'Task 1',
task_status: false
},
{
photo_urls: [],
task_name: 'Task 2',
task_status: false
}
]
},
{
maintask: 'Main task 2',
subtask: [
{
photo_urls: [],
task_name: 'Task 1',
task_status: false
},
{
photo_urls: [],
task_name: 'Task 2',
task_status: false
}
]
}
]
}
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case ON_CHECK_BOX_CLICK:
return state;
default:
return state;
}
};
I have an object of subtask in action.payload. First I want to check index of an object in array and then I want to update state of particuler array of object.

I'll suggest to use https://github.com/kolodny/immutability-helper As a payload you may send a result of the update function:
import update from 'immutability-helper';
update(currentState, {
tasks: {
tasksArray: {
0: {
subtask: {
1: {
task_name: { $set: 'New value for Task 2' }
}
}
}
}
}
});

Related

Redux How to insert new Sub data in state

I'm making a todo app and using redux for state management. My todo state is made up of nested arrays.
const initialState = {
todos: [
{
id: 1,
name: "task1",
subdata: [
{
id: 101,
name: "subtask1",
complete: false,
},
{
id: 102,
name: "subtask2",
complete: true,
},
],
},
{
id: 2,
name: "task2",
subdata: [
{
id: 103,
name: "subtask3",
complete: false,
},
{
id: 104,
name: "subtask4",
complete: true,
},
],
},
Reducers:
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
const newTodo = state.todos.concat(action.payload);
return { ...state, todos: newTodo };
case ADD_SUBTODO:
const newSubtodo = action.payload;
?????????????????????????????????????????????
How can i append new subtodo to initialstate?
I used the immer library, but I want to do it the traditional way, for example the spread operator. I would be glad if you help.
You could do something like...
// send payload as {id:1,newSubtodo: newSubtodo}
case ADD_SUBTODO:
const newSubtodo = action.payload.newSubtodo;
//id is the task/todo id of which you want to add a new subdata
const newTask = initialState.todos.find(i=>i.id==action.payload.id)
//id is the task/todo id of which you want to add a new subdata
newTask.subdata.push(newSubtodo)
return {...initialState,todos:[...initialState.todos,newTask]}
Note: Using nested objects as state in React Js is not a good
practice.

redux - how to remove element in array which is nested to another array

I want to remove item by sending action "itemRemoved", to which I pass the id of the board and the id of the element to be deleted
How to edit my Reducer "case ITEM_REMOVED"?
const initialState = {
boards: [
{
id: 1,
title: "1",
items: [
{ id: 1, title: "1" },
{ id: 2, title: "2" },
],
},
{
id: 2,
title: "2",
items: [
{ id: 3, title: "3" },
{ id: 4, title: "4" },
{ id: 5, title: "5" },
],
},
],
};
const actions = {
itemRemoved: (boardId, itemId) =>
({ type: ITEM_REMOVED, boardId, itemId }),
}
const boardsReducer = (state = initialState, action) => {
switch (action.type) {
case ITEM_REMOVED:
return {
...state,
boards: [] // <-- ??????????
};
default:
return state;
}
};
I'll try something like this:
const boardsReducer = (state = initialState, action) => {
switch (action.type) {
case ITEM_REMOVED:
return {
...state,
boards: state.boards.map(b => {
if (b.id !== action.boardId) {
return b
}
const newB = {...b}
newB.items = newB.items.filter(i => i !== action.itemId)
return newB;
})
};
default:
return state;
}
};

How to modify existing data in reducer?

I'm new to react-redux, I was working on a tutorial and I wanted to add a few features of my own.
How do I add a new method to add a new song to the existing array of song objects > I was confused because theres already a song key in combine reducers. What should I return/pass as a parameter to add a new song?
import { combineReducers } from "redux";
const songsReducer = () => {
return [
{
title: "song one",
duration: "4:30"
},
{
title: "song one",
duration: "4:00"
},
{
title: "song one",
duration: "3:28"
},
{
title: "song one",
duration: "3:50"
}
];
};
const selectedSongReducer = (selectedSong = null, action) => {
if (action.type === "SONG_SELECTED") {
return action.payload;
}
return selectedSong;
};
**const addSong = () => {};** // need help with this function
export default combineReducers({
songs: songsReducer,
selectedSong: selectedSongReducer
});
const addSong = song => ({
type: 'ADD_SONG',
payload: song,
});
const songsReducer = (songs = [], action) => {
switch (action.type) {
case 'ADD_SONG': {
return [...songs, action.payload.song]; //immutable
}
default: {
return [
{
title: 'song one',
duration: '4:30',
},
{
title: 'song one',
duration: '4:00',
},
{
title: 'song one',
duration: '3:28',
},
{
title: 'song one',
duration: '3:50',
},
];
}
}
};
The action will return a song with type ADD_SONG:
const addSong = (song) => {
return {
type: "ADD_SONG",
payload: {
song
}
}
}
In reducer:
const selectedSongReducer = (selectedSong = [], { type, payload = {} }) => {
if (type === "SONG_SELECTED") {
return payload;
}
if (type === "ADD_SONG") {
return [...selectedSong].push(payload.song) // Imutable selectedSong
}
return selectedSong;
};
Always use switch case for cleaner code
and return like this return [...selectedSong, action.payload];
to avoid mutation
const addSong = (song) => {
return {
type: "ADD_SONG",
payload: {
song
}
}
}
const selectedSongReducer = (selectedSong = [], action) => {
switch (action.type) {
case 'SONG_SELECTED': {
return [...selectedSong, action.payload.song];
}
default: null
}
};
const songsReducer = (song = [], action) => {
switch (action.type) {
case 'ADD_SONG': {
return [...songs, action.payload.song];
}
default: {
return [
{
title: 'song one',
duration: '4:30',
},
{
title: 'song one',
duration: '4:00',
},
{
title: 'song one',
duration: '3:28',
},
{
title: 'song one',
duration: '3:50',
},
];
}
}
};

State Not Changing Properly In React Redux

I am new to React Redux I have initialized state which is properly displaying by calling its respective reducer which job to return the initialize State. But after mutating the state the state most probably not changing and i cant figure it out why
import { combineReducers } from 'redux';
const initialState = {
songs:[
{title:'No Scrubs', duration: '4:05'},
]
}
const songReducers = (state = initialState)=>{
return state
}
const selectedSongReducer =(selectedSong=null,action)=>{
if(action.type==='SONG_SELECTED'){
return action.payload
}
return selectedSong;
}
const addSongReducer = (state=initialState,action)=>{
if(action.type==="ADD_SONG"){
return {
...state,
songs:[
...state.songs, {title:'All demo', duration: '4:05'}
]
}
}
return state;
}
export default combineReducers({
songs: songReducers,
selectedSong: selectedSongReducer,
addSong:addSongReducer
})
Your overall state shape will look like
state = {
songs: {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
},
selectedSong: null,
addSong: {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
}
}
after the very first reducer call. Is this what you want? Asking because you have
state = {
songs: {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
},
...
}
instead of just
state = {
songs: [
{ title: 'No Scrubs', duration: '4:05' },
]
...
}

can't change nested object in a reducer even when not mutating the objects and arrays

I'm trying to update a state inside a reducer, i know i shouldn't mutate the object or nested objects so I'm using map for arrays or object spread for objects. but it seems i can't really change a value that is deeply nested.
Beside the fact that i can't change the state, i really dislike how the code looks and especially the number of loops i need to do to just change one property. I feel like there is a better, readable and more performant way of doing this.
this is the state:
const items = [{
name: 'item 1',
id: 'item1',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag2',
name: 'tag 2'
}]
}, {
name: 'item 2',
id: 'item2',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag4',
name: 'tag 4'
}]
}];
this is the action i'm dispatching:
const action = {
type: 'CHANGE_TAG_NAME',
payload: {
itemId: 'item2',
tagId: 'tag4',
newTagName: 'tag 44444'
}
};
this is the reducer:
const itemsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const itemIndex = state.findIndex(item => item.id === action.payload.itemId);
const tagIndex = state[itemIndex].tags.findIndex(t => t.id === action.payload.tagId);
const nextTag = {
...state[itemIndex].tags[tagIndex],
name: action.payload.newTagName
};
const nextTags = [
...state[itemIndex].tags.slice(0, tagIndex),
nextTag,
...state[itemIndex].tags.slice(tagIndex + 1, ),
];
const nextItem = {
...state[itemIndex],
tags: nextTags
};
const nextState = [
...state.slice(0, itemIndex),
nextItem,
...state.slice(itemIndex + 1)
];
}
default:
return state;
}
};
Your reducer should work just fine, you just forgot to return nextState in your case block.
As for less iterations i suggests this pattern:
map over the items, if the current item's id is different from the itemId you have in the payload then return it as is.
If the item's id is the same then return a new object and then map over the tags, doing the same condition like you did with the item.
If the tag's id isn't the same as the tagId in the payload return it as is, if it does the same return a new object.
Here is a running example:
const items = [{
name: 'item 1',
id: 'item1',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag2',
name: 'tag 2'
}]
}, {
name: 'item 2',
id: 'item2',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag4',
name: 'tag 4'
}]
}];
const action = {
type: 'CHANGE_TAG_NAME',
payload: {
itemId: 'item2',
tagId: 'tag4',
newTagName: 'tag 44444'
}
};
const itemsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
itemId,
tagId,
newTagName
}
} = action;
const nextState = state.map(item => {
if (item.id !== itemId) return item;
return {
...item,
tags: item.tags.map(tag => {
if (tag.id !== tagId) return tag;
return {
...tag,
name: newTagName
}
})
}
});
return nextState;
}
default:
return state;
}
};
console.log(itemsReducer(items, action));
As for a more readable code, i suggest to use more reducers.
A thumb of rule i use is to create a reducer per entity:
itemsReducer,
itemReducer,
tagsReducer,
tagReducer.
This way each reducer will be responsible for its own data.
Here is a running example:
const items = [{
name: 'item 1',
id: 'item1',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag2',
name: 'tag 2'
}]
}, {
name: 'item 2',
id: 'item2',
tags: [{
id: 'tag1',
name: 'tag 1'
}, {
id: 'tag4',
name: 'tag 4'
}]
}];
const action = {
type: 'CHANGE_TAG_NAME',
payload: {
itemId: 'item2',
tagId: 'tag4',
newTagName: 'tag 44444'
}
};
const tagReducer = (state = {}, action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
newTagName
}
} = action;
const nextState = {
...state,
name: newTagName
}
return nextState;
}
default:
return state;
}
}
const tagsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
tagId
}
} = action;
const nextState = state.map(tag => {
if (tag.id !== tagId) return tag;
return tagReducer(tag, action);
});
return nextState;
}
default:
return state;
}
}
const itemReducer = (state = {}, action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const nextState = {
...state,
tags: tagsReducer(state.tags, action)
}
return nextState;
}
default:
return state;
}
}
const itemsReducer = (state = [], action) => {
switch (action.type) {
case 'CHANGE_TAG_NAME':
{
const {
payload: {
itemId
}
} = action;
const nextState = state.map(item => {
if (item.id !== itemId) return item;
return itemReducer(item, action)
});
return nextState;
}
default:
return state;
}
};
console.log(itemsReducer(items, action));
This pattern is often called reducer composition, and you don't have to include all of them in your root reducer, just use them as external pure functions that will calculate the relevant portion of the state for your other reducers.
You forgot the key word return
//.....
const nextState = [
...state.slice(0, itemIndex),
nextItem,
...state.slice(itemIndex + 1)
];
// HERE RETURN 🎈
return newState;
}
default:
return state;

Resources