Redux reducer with Immutable - javascript

I want to add a post to an array of posts in the reducer.
Normally I would just do this:
CREATE_NEW__POST_SUCCESS: {
return {
...state,
fetchingPosts: false,
error: null,
posts: [...state.posts, ...action.payload],
};
However my current project requires me to use Immutable.js
With Immutable the state is set with .set() or .merge() or .update()
case CREATE_NEW_POST_SUCCESS: {
return state
.set('fetchingPosts', false)
.set('posts', action.payload)
.set('postsError', null);
This overwrites the whole array of posts with just one post object.
I tried lots of things like
.set('posts' ,[...state.posts, ...action.payload])
But no joy

You can use updateIn similarly to the answer here.
case CREATE_NEW_POST_SUCCESS: {
return state
.set('fetchingPosts', false)
.updateIn(['posts'], posts => posts.push(action.payload))
.set('postsError', null);

I think state.merge() and concat() could work here:
case CREATE_NEW_POST_SUCCESS: {
return state.merge({
fetchingPosts: false,
posts: state.get('posts').concat(action.payload),
postError: null,
})
}

Related

spread state in react redux

I want to know what does ...state inside { ...state } do? Is it to change the value of the store to the initial value or to let the store be the latest value?
import * as actionType from "../actions/actionTypes";
const initialStore = {
roomsCount: 0,
resPerPage: 0,
rooms: [],
filteredRooms: 0,
error: null,
success: false,
};
const reducer = (state = initialStore, action) => {
switch (action.type) {
case actionType.ALL_ROOM_SUCCESS:
return {
...state,
success: true,
rooms: action.rooms,
roomsCount: action.roomsCount,
resPerPage: action.resPerPage,
filteredRooms: action.filteredRooms,
};
case actionType.ALL_ROOM_FAILED:
return {
...state,
error: action.err,
};
}
};
If at first I use this reducer, it'll be successful so success will be true and error will be null. But if it fails the 2nd time and I use ...state in this situation, what is the success value? Is it the initial value (false) or does it keep the value from the first request (true)?
That is called the spread operator and it basically allows you to "clone" the fields of one object into a new object.
In your example, { ...state, error: action.err } means "copy all fields from state, but set the field error to action.err". It's very handy for this kind of logic where you want to change a very few fields but otherwise want to keep the original data.

How can I update only part of the state

Here is the State object:
const initialState = {
data: {
user: '',
token: '',
}
}
Reducer:
case 'DO_SOMETHING':
return {...state, data: action.payload }
If I soft copy the state as shown above, I will overwrite the entire data part of the state. How can I update only the user with the given payload without overwriting the token?
If the payload is just the user, then simply use the same destructuring pattern you used for the state object in general:
return {
...state,
data: {
...state.data,
user: action.payload
}
}
This pattern can be nested as much as you like, so you can have large structured state objects and just pass around the fields you want in the payloads.
Try this, (considering action.payload is the entire data object)
case 'DO_SOMETHING':
return {...state, data: { ...state.data, user: action.payload.user } }
Spread the nested object too:
return {...state, data: {...state.data, user: action.payload} }

React Redux - Reducer with CRUD - best practices?

I wrote simple reducer for User entity, and now I want to apply best practices for it, when switching action types and returning state. Just to mention, I extracted actions types in separate file, actionsTypes.js.
Content of actionsTypes.js :
export const GET_USERS_SUCCESS = 'GET_USERS_SUCCESS';
export const GET_USER_SUCCESS = 'GET_USER_SUCCESS';
export const ADD_USER_SUCCESS = 'ADD_USER_SUCCESS';
export const EDIT_USER_SUCCESS = 'EDIT_USER_SUCCESS';
export const DELETE_USER_SUCCESS = 'DELETE_USER_SUCCESS';
First question, is it mandatory to have actions types for FAILED case? For example, to add GET_USERS_FAILED and so on and handle them inside usersReducer?
Root reducer is:
const rootReducer = combineReducers({
users
});
There is code of usersReducer, and I put comments/questions inside code, and ask for answers (what are best practices to handle action types):
export default function usersReducer(state = initialState.users, action) {
switch (action.type) {
case actionsTypes.GET_USERS_SUCCESS:
// state of usersReducer is 'users' array, so I just return action.payload where it is array of users. Will it automatically update users array on initial state?
return action.payload;
case actionsTypes.GET_USER_SUCCESS:
// What to return here? Just action.payload where it is just single user object?
return ;
case actionsTypes.ADD_USER_SUCCESS:
// what does this mean? Can someone explain this code? It returns new array, but what about spread operator, and object.assign?
return [...state.filter(user => user.id !== action.payload.id),
Object.assign({}, action.payload)];
case actionsTypes.EDIT_USER_SUCCESS:
// is this ok?
const indexOfUser = state.findIndex(user => user.id === action.payload.id);
let newState = [...state];
newState[indexOfUser] = action.payload;
return newState;
case actionsTypes.DELETE_USER_SUCCESS:
// I'm not sure about this delete part, is this ok or there is best practice to return state without deleted user?
return [...state.filter(user => user.id !== action.user.id)];
default:
return state;
}
}
I'm not an experienced developer but let me answer your questions what I've learned and encountered up to now.
First question, is it mandatory to have actions types for FAILED case?
For example, to add GET_USERS_FAILED and so on and handle them inside
usersReducer?
This is not mandatory but if you intend to give a feedback to your clients it would be good. For example, you initiated the GET_USERS process and it failed somehow. Nothing happens on client side, nothing updated etc. So, your client does not know it failed and wonders why nothing happened. But, if you have a failure case and you catch the error, you can inform your client that there was an error.
To do this, you can consume GET_USERS_FAILED action type in two pleases for example. One in your userReducers and one for, lets say, an error or feedback reducer. First one returns state since your process failed and you can't get the desired data, hence does not want to mutate the state anyhow. Second one updates your feedback reducer and can change a state, lets say error and you catch this state in your component and if error state is true you show a nice message to your client.
state of usersReducer is 'users' array, so I just return
action.payload where it is array of users. Will it automatically
update users array on initial state?
case actionsTypes.GET_USERS_SUCCESS:
return action.payload;
This is ok if you are fetching whole users with a single request. This means your action.payload which is an array becomes your state. But, if you don't want to fetch all the users with a single request, like pagination, this would be not enough. You need to concat your state with the fetched ones.
case actionsTypes.GET_USERS_SUCCESS:
return [...state, ...action.payload];
Here, we are using spread syntax.
It, obviously, spread what is given to it :) You can use it in a multiple ways for arrays and also objects. You can check the documentation. But here is some simple examples.
const arr = [ 1, 2, 3 ];
const newArr = [ ...arr, 4 ];
// newArr is now [ 1, 2, 3, 4 ]
We spread arr in a new array and add 4 to it.
const obj = { id: 1, name: "foo, age: 25 };
const newObj = { ...obj, age: 30 };
// newObj is now { id: 1, name: "foo", age: 30 }
Here, we spread our obj in a new object and changed its age property. In both examples, we never mutate our original data.
What to return here? Just action.payload where it is just single user
object?
case actionsTypes.GET_USER_SUCCESS:
return ;
Probably you can't use this action in this reducer directly. Because your state here holds your users as an array. What do you want to do the user you got somehow? Lets say you want to hold a "selected" user. Either you can create a separate reducer for that or change your state here, make it an object and hold a selectedUser property and update it with this. But if you change your state's shape, all the other reducer parts need to be changed since your state will be something like this:
{
users: [],
selectedUser,
}
Now, your state is not an array anymore, it is an object. All your code must be changed according to that.
what does this mean? Can someone explain this code? It returns new
array, but what about spread operator, and object.assign?
case actionsTypes.ADD_USER_SUCCESS:
return [...state.filter(user => user.id !== action.payload.id), Object.assign({}, action.payload)];
I've already tried to explain spread syntax. Object.assign copies some values to a target or updates it or merges two of them. What does this code do?
First it takes your state, filters it and returns the users not equal to your action.payload one, which is the user is being added. This returns an array, so it spreads it and merges it with the Object.assign part. In Object.assign part it takes an empty object and merges it with the user. An all those values creates a new array which is your new state. Let's say your state is like:
[
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
]
and your new user is:
{
id: 3, name: "baz"
}
Here what this code does. First it filters all the user and since filter criteria does not match it returns all your users (state) then spread it (don't forget, filter returns an array and we spread this array into another one):
[ { id: 1, name: "foo"}, { id: 2, name: "bar" } ]
Now the Object.assign part does its job and merges an empty object with action.payload, a user object. Now our final array will be like this:
[ { id: 1, name: "foo"}, { id: 2, name: "bar" }, { id: 3, name: "baz" } ]
But, actually Object.assign is not needed here. Why do we bother merging our object with an empty one again? So, this code does the same job:
case actionsTypes.ADD_USER_SUCCESS:
return [...state.filter(user => user.id !== action.payload.id), action.payload ];
is this ok?
case actionsTypes.EDIT_USER_SUCCESS:
const indexOfUser = state.findIndex(user => user.id === action.payload.id);
let newState = [...state];
newState[indexOfUser] = action.payload;
return newState;
It seems ok to me. You don't mutate the state directly, use spread syntax to create a new one, update the related part and finally set your state with this new one.
I'm not sure about this delete part, is this ok or there is best
practice to return state without deleted user?
case actionsTypes.DELETE_USER_SUCCESS:
return [...state.filter(user => user.id !== action.user.id)];
Again, it seems ok to me. You filter the deleted user and update your state according to that. Of course there are other situations you should take into considiration . For example do you have a backend process for those? Do you add or delete users to a database? If yes for all the parts you need to sure about the backend process success and after that you need to update your state. But this is a different topic I guess.

React Redux state not replacing

I am trying to fetch a new batch of products through an action and then replace the state with the new batch, however it just adds it to the array...
Here's my reducer for controlling the state:
const initialState = {
fetching: false,
fetched: false,
Brands:[],
Markings:[],
Products: [],
error: null
}
export default function reducer(state=initialState, action=null) {
switch (action.type){
case "FETCH_PRODUCTS_PENDING" : {
return {...state}
break;
}
case "FETCH_PRODUCTS_REJECTED" : {
return {...state}
break;
}
case "FETCH_PRODUCTS_FULFILLED" : {
return{ ...state,
Products: state.Products.concat(action.payload.data.Products),
Brands: action.payload.data.Facets[0].Facets,
Markings: action.payload.data.Facets[1].Facets
}
break;
}
}
return state
}
It goes wrong in the fulfilled case.. I am not sure how this "...state" works, do I need to do a object assign or something?
Upon load I get 52 products, when trying to request a new batch it adds it up so my this.props.products is 104 items... I want it to replace
This line is the culprit
Products: state.Products.concat(action.payload.data.Products),
In this case all you want to do is replace the Products array with the one from the action.
So it should be simply
Products: action.payload.data.Products
See here for a nice explanation on the spread operator:
https://ponyfoo.com/articles/es6-spread-and-butter-in-depth

How do I only update one value of my reducer in Redux?

Below is my code
function customMsg(state, action) {
state = state || {
person: {
isFetching: false,
didInvalidate: false,
name: "",
height: "",
}
};
switch(action.type) {
case ACTION_TYPES.PERSON.FETCH_PERSOn_CONTENT_SUCCESS:
return $.extend({}, state, {
person.name: action.result.name
});
default:
return state;
}
}
How do I only update one value of my reducer in Redux?
Above is a example which i only want to update the name of person object.
How can i do that?
Using jQuery extend, create a clone and set person name:
var newState = $.extend({}, state);
newState.person.name = action.result.name;
return newState;
Otherwise, to clone deeply an object you can use lodash cloneDeep().
Another way is to use immutableJS to set your app state as immutable. It is much more bug "mutable" proof and offers functions to set deep nested value in an immutable. See updateIn:
return state.updateIn(['person', 'name'], () => action.result.name);
Try it!

Categories

Resources