How to update the state of an object in react-redux - reactjs

My question might sound stupid but I swear my brain freeze, I google it for answer but honestly I don't even know what should I search for. So basically I have a reducer, usually when I'm using redux I'm using arrays and my reducers look like this:
import { FETCH_ALL, CREATE, UPDATE, DELETE } from '../constants/actionTypes';
export default (posts = [], action) => {
switch (action.type) {
case FETCH_ALL:
return action.payload;
case CREATE:
return [...posts, action.payload];
case UPDATE:
return posts.map((post) => post._id === action.payload._id ? action.payload : post);
case DELETE:
return posts.filter((post) => post._id !== action.payload);
default:
return posts;
}
};
And now I only have an object, and I really don't know what to write on case UPDATE, I mean I'm not looping through and find my updated object and that process.
My question is how can I update if I only have an object
import { FETCH_PROFILE, UPDATE_PROFILE } from '../constants';
export default (profile= {}, action) => {
switch (action.type) {
case FETCH_PROFILE:
return action.payload;
case UPDATE_PROFILE:
return ;
default:
return profile;
}
};
like here, in that case UPDATE_PROFILE, what should I write to make it work, in my backend everything going well, the return is an object with the updated user.
I know my question is stupid, but please be kind! Thank you in advance
Unexpected token, expected "," (8:32)
6 | return action.payload;
7 | case UPDATE_PROFILE:
> 8 | return {...profile, action.payload};
| ^
9 | default:
10 | return profile;
11 | }

For object you will use spread operator just like in an array:
import { FETCH_PROFILE, UPDATE_PROFILE } from '../constants';
export default (profile= {}, action) => {
switch (action.type) {
case FETCH_PROFILE:
return action.payload;
case UPDATE_PROFILE:
return {...profile, action.payload};
default:
return profile;
}
};
More about it here.

In your update profile, pass the entire object after updating.
For eg:
let profiles =[{name:"foo",id:1},{name:"bar",id:2},{name:"baz",id:3}]
let updateProfile = [{id:2,name:"gaurav"}]
then, first update the profiles with updated profile, that is,
const updatedProfiles = profiles.map(item=>item.id===?:updatedProfile:item)
then, simply pass updatedProfiles in action.payload,
in your reducer, it will be then
case UPDATE_PROFILE:
return action.payload;

Related

Update one of the objects in array, in an immutable way with immer.js

Say I have some initial state like
const initialState = {
post: { comments: {
{0: {id:'1',name:'myname',statusdata: false}},
{1: {id:'2',name:'yourname',statusdata: true}},
},
};
And I want add to data as the result of an action but the data I want to add is going to be an array. How do I go about this?
export default produce((draft, action) => {
switch (action.type) {
case UPDATE_NAME:
draft.posts.comments.name = action.payload;
break;
case CHANGE_STATUS:
draft.posts.comments.statusdata = !action.payload;
break;
default:
}
}, initialState);
I have this error
Error: [Immer] Immer only supports setting array indices and the 'length' property
I have made assumptions on the schema of comments and action.payload. Try the following solution.
Object.values(draft.post.comments).map((comment){
if(comment.id == action.payload.id){
const updatedComment = Object.extend({}, comment, {"name": action.payload.name});
return updatedComment;
}
return comment;
})
I'm try
export default produce((draft, action) => {
switch (action.type) {
case CHANGE_STATUS:
draft.posts.comments[
draft.posts.comments.findIndex(i => i.id === action.payload)
].statusdata = action.payload;
return draft;
default:
}
}, initialState);

React + Redux, how can I add or omit an object from a specific property in a reducer

I have the following reducer:
import {CREATE_CATEGORY, EDIT_CATEGORY, DELETE_CATEGORY, FETCH_CATEGORY, FETCH_CATEGORIES, FETCH_TOP_CATEGORIES} from "../actions/categoriesActionTypes";
import _ from 'lodash';
export default (state = [], action) => {
switch (action.type) {
case CREATE_CATEGORY:
case EDIT_CATEGORY:
case FETCH_CATEGORY:
return {
...state,
[action.payload.id]: action.payload
};
case DELETE_CATEGORY:
return _.omit(state, action.payload);
case FETCH_CATEGORIES:
return {...state, categories:_.mapKeys(action.payload, 'id')};
case FETCH_TOP_CATEGORIES:
return {...state, topCategories: _.mapKeys(action.payload, 'id')};
default:
return state;
}
}
which is called by two actions, one to FetchTopCategories which updates the topCategories key. The Fetch Categories action fetches categories which updates the categories key.
I can work out how to add or omit a category from the categories key when CREATE, EDIT, FETCH or DELETE category is called.
The state looks like this:
categories:
{
topCategories: {[]},
categories: {[]]
}
I have copied the code for this reducer from another reducer that does not have sub properties like this one, but I want to do this to keep everything 'category' related in the same reducer.
I'm sure I just need to add some code similar to FetchCategories and FetchTopCategories in the remaining action types but I cant work out the syntax.
I'm trying to do something like:
case CREATE_CATEGORY:
case EDIT_CATEGORY:
case FETCH_CATEGORY:
return {
...state,
categories:[action.payload.id]: action.payload
};
case DELETE_CATEGORY:
return categories:_.omit(state, action.payload);
Ok, I think I have figured it out now.
import {CREATE_CATEGORY, EDIT_CATEGORY, DELETE_CATEGORY, FETCH_CATEGORY, FETCH_CATEGORIES, FETCH_TOP_CATEGORIES} from "../actions/categoriesActionTypes";
import _ from 'lodash';
const initState = {
categories: []
};
export default (state = initState, action) => {
switch (action.type) {
case CREATE_CATEGORY:
case EDIT_CATEGORY:
case FETCH_CATEGORY:
return {
...state,
categories: {
...state.categories,
[action.payload.id]: action.payload
}
};
case DELETE_CATEGORY:
return {
...state,
categories: _.omit(state.categories, action.payload),
topCategories: _.omit(state.topCategories, action.payload)
};
case FETCH_CATEGORIES:
return {...state, categories:_.mapKeys(action.payload, 'id')};
case FETCH_TOP_CATEGORIES:
return {...state, topCategories: _.mapKeys(action.payload, 'id')};
default:
return state;
}
}

Redux reducer isn't returning the updated state to UI

I have a reducer case, and this case is supposed to add a comment, and update the UI with the new comment,
Pretty much you add a comment, and it will show under the rest of comments once you do, the logic is not returning the new state for some reason.
What could be the cause of this ? And to be honest im unsure of what im doing, im fairly new when it comes to reducer normalizer state updates.
Only on refresh i see the new comment.
const initialState = {
allIds:[],
byId:{},
};
const allIds = (state = initialState.allIds, action) => {
switch (action.type) {
case FETCH_IMAGES_SUCCESS:
return action.images.reduce((nextState, image) => {
if (nextState.indexOf(image.id) === -1) {
nextState.push(image.id);
}
return nextState;
}, [...state]);
case UPLOAD_IMAGE_SUCCESS:
console.log(action.data)
return [action.data.id, ...state];
case POST_COMMENT_SUCCESS:
console.log(action)
return [action.data.id, ...state];
default:
return state;
}
}
const image = (state = {}, action) => {
switch (action.type) {
case POST_COMMENT_SUCCESS:
return [...state.comments, action.data, ...state.comments]
default:
return state;
}
}
const byId = (state = initialState.byId, action) => {
switch (action.type) {
case FETCH_IMAGES_SUCCESS:
return action.images.reduce((nextState, image) => {
nextState[image.id] = image;
return nextState;
}, {...state});
case POST_COMMENT_SUCCESS:
console.log(action.data) // renders new commnent
return {
...state,
...state.comments,
[action.data.id]: action.data,
}
case UPLOAD_IMAGE_SUCCESS:
console.log(action)
return {
...state,
[action.data.id]: action.data,
};
default:
return state;
}
}
Not sure because I don't see the shape of your byId object, but I think the problem might be here:
case POST_COMMENT_SUCCESS:
return {
...state, // here you are spreading the whole state object
// and here you are spreading state.comments at the same level, NOT nested:
...state.comments,
// Again same thing here, not nested:
[action.data.id]: action.data,
}
Instead you should be doing something like this, more info in the Redux docs:
case POST_COMMENT_SUCCESS:
return {
...state,
comments: {
...state.comments,
[action.data.id]: action.data
}
}
Alternatively, you could use Immer to simplify your reducers, I'm using it and I'm loving it. It feels a little bit weird because you can use mutation methods to modify its draft but it is great if you want to have simpler reducers. Your code with Immer would be something much more simpler:
case POST_COMMENT_SUCCESS:
draft.comments[action.data.id]: action.data,
return;
With Immer you just have to modifiy (or mutate) the draft object, if the value you are assigning to is different from the one you have in state, it will generate a new object for you, otherwise it will return state.
Hope it helps.

Redux adds duplicate array item when loaded from state

Hey everyone probably a simple question, basically I have a button when i click it fires an action and passes down the whole object that I concat to array if its not duplicate but strangely what happens because I save data to local-storage and after I load it from there it does not check for duplicate and duplicates the array item. My reducer code below maybe the error is there?
Searched as much as possible.
const initialState = {
favourites: []
};
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
state.favourites.indexOf(payload) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};
The issue here seems to be that state.favourites.indexOf(payload) === -1 is always true. This is because the function Array.prototype.findIndex() does not find identical objects.
You should use an alternate method of checking to see if the payload object is already in the favourites array. For example, you could try something like this:
const favourites = (state = initialState, action) => {
const { payload } = action;
switch (action.type) {
case actionTypes.ADD_FAVOURITES:
return {
...state,
favourites:
JSON.stringify(state.favourites).indexOf(JSON.stringify(payload)) === -1
? state.favourites.concat(payload)
: state.favourites
};
default:
return state;
}
};

Changing an array in a reducer when an item has been deleted or updated in react/redux

I am using react/redux and have been looking at ways to update my array of posts in my reducer if a post is edited or deleted.
Is there a more simple way rather than doing something with creating an index function which finds the index of the post you are trying to delete/update, then returning the current state with the updated array using slice?
This is what I have so far in my reducer, I am kind of stuck:
import constants from '../constants';
const initialState = {
all: [],
sorted: [],
};
export default (state = initialState, action) => {
const newState = Object.assign({}, state);
switch (action.type) {
case constants.CREATE_OFFER:
newState[action.payload.id] = action.payload;
newState.all.unshift(action.payload);
return newState;
case constants.GET_OFFERS:
newState.all = action.payload;
return newState;
case constants.GET_OFFER:
newState[action.payload.id] = action.payload;
return newState;
case constants.EDIT_OFFER:
newState[action.payload.id] = action.payload;
return newState;
case constants.DELETE_OFFER:
newState[action.payload.id] = action.payload;
return newState;
case constants.SORT_OFFERS:
newState.sorted = action.payload;
return newState;
default:
return state;
}
};
Instead of :
newState[action.payload.id] = action.payload;
return newState;
You can use ES6 sytax :
return {...state, [action.payload.id]: action.payload.data};
i believe if you use Object as a app state rather than an array , it will be easy to update , delete all you need to do is use .mapKeys method of lodash library along with property you want to extract from your array (ideally 'id')
import _ from 'lodash';
return _.mapKeys(action.payload.data, 'id');
this will return :
{
'1': { id: 1,
title: 'Hello!',
},
'2': {
id: 2,
title: 'World'
}
}
You can read more Here
I think, everything is good, but it will be better to use es6 spread operator, your code will look more clear
case constants.CREATE_OFFER:
return {
...state,
all: state.all.concat(action.payload),
[action.payload.id]: action.payload
}
case constants.EDIT_OFFER:
return {
...state,
[action.payload.id]: action.payload,
all: state.all.map( item => item.id === action.payload.id?action.payload: item )
}
case constants.DELETE_OFFER:
const new = {
...state,
all: state.all.filter( item => item.id !== action.payload.id)
};
delete new[action.payload.id];
return new;
}

Resources