Access object component in redux state reducers - reactjs

I have just started to learn redux and I was wondering how to access object component in redux reducers. I want to access the address component inside the user but is unable to because it is the key. Is there a way to access it? Here is my code:
const initialState = {
token: localStorage.getItem('token'),
user: {},
};
export default function (state = initialState, action) {
const { type, payload } = action;
case SET_SHIPPING_ADDRESS:
return {
...state,
state.user.addresses: payload, //how to access the addresses property from here?
}
};

The spread operation "..." lets you assign a new value to the properties of an object.
if one of the properties is an object and you want to assign a new value to one of its properties, you need to spread it as well and so on.
For example:
export default function (state = initialState, action) {
const { type, payload } = action;
case SET_SHIPPING_ADDRESS:
return {
...state,
user: {
...state.user,
addresses: payload
}
}
};

You have a user property in your initial state but then you try to assign another property state.user.buyer.addresses which is wrong because what you actually want to do is update the addresses inside the user object.
Easy and quick way to deep clone your user object would be to stringify it and then parse it.
const clone = JSON.parse(JSON.stringify(state.user));
then you could assign the new addresses
clone.buyer.addresses = payload.addresses
Finally update your state with the new user object
case SET_SHIPPING_ADDRESS:
return {
...state,
user: clone
}

Related

Update state using array.find for redux return reference or value?

I am following the tutorial on the official website.
I know that the state is immutable and the operation in reducer is actually create a new copy of the state, modify it and replace the original one.
I am thinking:
isn't it suppose to delete the entry(exisitngPost) in the array and push a new one in instead? As state.find() should return by value instead of reference. Do I get it wrong? or it is something to do with reducer logic?
tutorial link
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
postAdded(state, action) {
state.push(action.payload)
},
postUpdated(state, action) {
const { id, title, content } = action.payload
// where i find confused<<<<<<<<<<<<<<<<<<<
const existingPost = state.find(post => post.id === id)
if (existingPost) {
existingPost.title = title
existingPost.content = content
}
}
}
})
export const { postAdded, postUpdated } = postsSlice.actions
export default postsSlice.reducer
state.find returns the object in state there - and if it is not a primitive, that object will be a reference. So at that point in a RTK immer reducer you can modify it and after the reducer is run, a new immutable copy with the applied changes will be created for you instead of modifying the original.

How to replace an entire array in a redux reducer while allowing application to re-render?

In one of my application's reducers, I require to replace an entire array. Hence, I have written my reducer like this:
const arrayReducerDefaultState = []
const arrayReducer = (state = arrayReducerDefaultState, action) =>{
switch(action.type){
case "CHANGE_ARRAY":
return{
...state,
array: action.array
}
default:
return state
}
}
export default arrayReducer
When the action is called, I see the array is being updated (using Redux Dev Tools). But the application does not render again when the 'array' value is updated.
How do I change my reducer so that I can keep replacing my entire array while allowing the application to re-render when the store changes.
EDIT 1:
const arrayReducerDefaultState = {
array: []
}
const arrayReducer = (state = arrayReducerDefaultState, action) =>{
switch(action.type){
case "CHANGE_ARRAY":
return {
...state,
array: action.array
}
default:
return state
}
}
Since I was having problems working with the default state being an array, I made it such that it is an object with an array in it. 'array' now is not undefined but changing it still does not re-render the application.
Tour state is array, but you return object in "CHANGE_ARRAY" case.
const arrayReducerDefaultState = []
const arrayReducer = (state = arrayReducerDefaultState, action) =>{
switch(action.type){
case "CHANGE_ARRAY":
return action.array
default:
return state
}
}
export default arrayReducer
Actually you can have mistake in selector.
PS action has struct like object where must be type (required), payload (optinal) - there is you data, meta (optinal) - another additional inforamtion
The above solution works, but my problem was somewhere else.

How can you replace entire state in Redux Toolkit reducer?

EDIT: The solution is to return state after I replace it completely (return state = {...action.payload})! But why? I don't need to return it when I replace the fields individually.
I'm working with the Redux Toolkit, which simplifies some Redux boilerplate. One thing they do is use Immer to allow you to directly 'modify' state (in fact, you're not). It works fine, except I don't know how to replace my section of state entirely. For instance, I want to do something like this
const reducer = createReducer({ s: '', blip: [] }, {
[postsBogus.type]: (state, action) => {
state = { ...action.payload };
}
but state remains untouched. Instead I have to do this
[postsBogus.type]: (state, action) => {
state.s = action.payload.s;
state.blip = action.payload.blip;
}
Is there a way I can replace state entirely?
Yes, as you noted, you must return a new value to replace the state entirely.
Even in a "plain" Redux reducer, assigning state = newValue does nothing, because all that does is say that the local function variable named state is now pointing to a different value in memory. That does nothing to return a new value.
For Immer specifically, you can either mutate the contents of the Proxy-wrapped state value as long as it's an object or array, or you can return an entirely new value, but not both at once.
You can, but not in this way, when you say:
function x(y){
y = 4
}
You're mutating the function parameter, but not the state of redux,
you have two options to update this state of your redux store:
either to set state.your_state_name = something or, in your case, what you want to do is to return a new object, the new object is what the new state value is going to be.
simple example:
myReducerFunc: (state, action) => {
return {...action.payload }
},
another example:
const loggedInUserSlice = createSlice({
name: '$loggedInUser',
initialState: {
isLoggedIn: false,
},
reducers: {
loggedIn: (state, action) => {
return {
isLoggedIn: true,
...action.payload,
}
},
},
})

Class object not working properly in reducer

I have a user model (es6 class) and I'm creating a object using the new keyboard and passing that to the initialState to my userReducer function. How can I update the model based on action.
E.g. If I try to dispatch an action to change the isLogging in userModel then the prevState and nextState is same in logger.
https://i.ibb.co/0CBSZ5v/Screenshot-from-2019-04-19-19-07-44.png
User Reducer
import { USER } from '../constants'
import type { IUserInitialState, IUserAction } from '../types'
import { UserModel } from '../models'
const initialState: IUserInitialState = new UserModel()
export const userReducer = (state: IUserInitialState = initialState, action: IUserAction): Object => {
console.log(state)
switch (action.type) {
case USER.LOGIN_REQUEST:
console.log(state)
initialState.userIsLogging = action.payload
return initialState
default:
return state
}
}
------------------------------
User Action
export const loginRequest = (type: boolean): Object => {
return {
type: USER.LOGIN_REQUEST,
payload: type
}
}
User Model
export class UserModel {
user: IUserModel = {
username: '',
password: '',
isLogging: false
}
set userModel(userObject: IUserModel) {
this.user = userObject
}
set userIsLogging(logging: boolean) {
this.user.isLogging = logging
}
get userIsLogging() {
return this.user.isLogging
}
}
[1]: https://i.ibb.co/0CBSZ5v/Screenshot-from-2019-04-19-19-07-44.png
You are using reducer wrong.
1- When you create a state, make sure it's just a primitive type without any methods.
2- A reducer is responsible of creating a new state on any action. But you are only returning initial state. You should have something like
case USER.LOGIN_REQUEST:
console.log(state)
initialState.userIsLogging = action.payload
return {
...state,
userIsLogging: action.payload,
}
3- You might want to check sagas. You don't have to handle all this async logic yourself
A reducer must be a pure function. It should returns a new state based on a previous state and an action.
Please do not mutate the previous state. Instead, create a new instance, make the changes you want there and finally returns this new instance.
I would highly recommend you to watch the videos from the redux github page. No better explanation that the one from the redux author.

How to connect Reused Reducers Logic into React components?

I'm using the pattern described here that show us how to reuse reducer logic for other similar purposes.
So, my reducer code is like the code below:
function ContentFilterReducer(entity = ''){
initialState.groupFilter = entity;
return function ContentFilterReducer(state = initialState, action)
{
// is the entity that we want to update?
if (action.item !== undefined && action.item.groupFilter !== entity)
return state;
switch (action.type) {
case ContentFilterTypes.ADD_ITEM:
return {
// we set the
groupFilter: action.item.groupFilter,
listObjects : state.listObjects.push(new Map({
id: action.item.id,
description: action.item.description,
imgSrc: action.item.imgSrc
}))
}
default:
return state;
}
}
}
My combinedReducer describe a reducer for each purpose, as we can see below:
const SearchReducers = combineReducers({
// contains all allowed filters to be selected
UsersContentFilterReducer : ContentFilterReducer(Types.users),
OrganizationsContentFilterReducer : ContentFilterReducer(Types.organizations)
})
Everything is working great, however I'd like to know, how to connect it in a React component using the connect function from React-Redux?
As we can see, I can define the reducer setting an entity (a simple char like 'a', 'o', etc) and, to call the specific reducer, I need only set the entity in my action. And now, the problem is how to connect a specific reducer for a specific presentational component?
The code below is my HOC container that connect the reducer to a specific component, however, the code is the old version, without defining wich reducer should call.
const mapStateToProps = (state, action) => {
return {
contentList: ContentFilterReducer(state.ContentFilterReducer, action)
}
}
/**
*
* #param {contains the action that will be dispatched} dispatch
*/
const mapDispatchToProps = (dispatch) => {
return {
onAddClick: (groupFilter, filterDescription, operator, value) => {
dispatch(AddFilter(groupFilter, filterDescription, operator, value));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ContentFilterField)
You don't connect a reducer. You connect a component to the Redux store. I won't name my state xxxReducer, it's a little bit confusing.
I'm not sure what your app looks like, for a simple case, you just need to: (connect both state)
const mapStateToProps = (state) => {
return {
userContentList: state.SearchReducers.UsersContentFilterReducer,
organizationContentList: state.SearchReducers.OrganizationsContentFilterReducer,
}
}
If you want to switch between usersContent and organizationsContent dynamically based on your component's state, what you need is a selector function.
This is the official redux example: https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/index.js#L10-L26
These functions are selectors, you import and use them to get the state you want.
So you will create something like getContentList and it accepts a type like Types.users
const mapStateToProps = (state) => {
return {
// suppose you save current type saved in SearchReducers.type
contentList: getContentList(state.SearchReducers.type)
}
}
Also, the second parameter of mapStateToProps is ownProps not action.

Resources