how do i update a specific property of object in a state?
i need to update the property offerPrice but still want to retain the other state values in the object
this is what i have in my effects
case OffersActionTypes.SET_TOTAL_OFFER_PRICE:
return {
...state, offerDetails: {
...state, singleOfferDetails: [
...state.offerPrice, action.payload
]
}}
but on the redux tools it giving me this
If you look carefully, you're not copying the parts of state that need to be copied. I believe you want something more like:
return {
...state,
offerDetails: {
...state.offerDetails,
singleOfferDetails: {
...state.offerDetails.singleOfferDetails,
offerPrice: action.payload
}
}
}
Of course, this can also be achieved with something like:
const newState = { ...state };
newState.offerDetails.singleOfferDetails.offerPrice = action.payload;
return newState;
I would argue, however, that your store is too embedded. Generally you want to keep things as flat as possible, although I'll grant you that it's not always a simple process. Best of luck!
I have come across two kinds of reducer design for handling a large state within a single module.
The first approach is to have all the variables inside a single large state and have one reducer function.
const initialState = {
results: [],
pagination: {},
filters: [],
appliedFilters = [],
}
const reducer = (st = { ...initialState }, action) => {
const state = st;
switch (action.type) {
case 'SEARCH':{
return {
...state,
results: action.results,
pagination: action.pagination,
filters: action.filters,
appliedFilters: action.appliedFilters
},
case 'APPLY_FILTER':{
return {
...state,
results: action.results,
pagination: action.pagination,
filters: action.filters,
appliedFilters: action.appliedFilters
},
case 'PAGINATE':{
return {
...state,
results: action.results,
pagination: action.pagination,
}
}
The second approach is to have multiple reducers for the sub items in the data.
export function applications(state = [], { type, results}) {
switch (type) {
case SEARCH:
return results;
case INIT_RESULTS:
return [];
default:
return state;
}
}
export function pagination(state = null, { type, paginationData }) {
switch (type) {
case SEARCH:
return paginationData;
default:
return state;
}
}
export function filters(state = [], { type, filterData }) {
switch (type) {
case SEARCH:
return filterData;
case UPDATE_FILTERS:
return filterData;
default:
return state;
}
}
I think both have their own pros and cons. Considering scalability and modularization which one is a better pick?
Generally, both of these are very far off our official recommendations.
you should have a "slice" reducer for each sub-state (that rules out your first option
you should not treat reducers as "setting a value", but move the whole "calculating how to get the value" into the reducer and handle your action as just "describing an event that happened"
you should be using the official Redux Toolkit which we are recommending & teaching as the default way of writing Redux sinde 2019. Seriously, look at it. It is about 1/4 of the code. No more switch..case reducers or ACTION_TYPES.
Please give the Redux Style Guide a read and to learn modern Redux with Redux Toolkit, please follow the official Redux Tutorial
The Redux toolkit docs mention using actions (or rather action types) in multiple reducers
First, Redux action types are not meant to be exclusive to a single slice. Conceptually, each slice reducer "owns" its own piece of the Redux state, but it should be able to listen to any action type and update its state appropriately. For example, many different slices might want to respond to a "user logged out" action by clearing data or resetting back to initial state values. Keep that in mind as you design your state shape and create your slices.
But, “keeping that in mind”, what is the best way to achieve this, given that the toolkit puts the slice name at the start of each action type? And that you export a function from that slice and you call that single function to dispatch the action? What am I missing? Does this have to be done in some way that doesn’t use createSlice?
It looks like this is what extraReducers is for:
One of the key concepts of Redux is that each slice reducer "owns" its slice of state, and that many slice reducers can independently respond to the same action type. extraReducers allows createSlice to respond to other action types besides the types it has generated.
It is a little strange that the action dispatcher should know which reducer the action belongs. I'm not sure the motivation of having reducers and extraReducers, but you can use extraReducers to allow several slices to respond to the same action.
I've found that using the extraReducers functionality when creating a slice with createSlice is the best way to do it.
In my case I've implemented this by creating a 'SliceFactory' class for each related feature. I've used it to do exactly what is in the example and reset relevant slices on user logout by listening for a LOGOUT_USER action.
Reference:
extraReducers: https://redux-toolkit.js.org/api/createSlice#extrareducer
Original article I used for the factory: https://robkendal.co.uk/blog/2020-01-27-react-redux-components-apis-and-handler-utilities-part-two
import { createSlice } from '#reduxjs/toolkit';
import { LOGOUT_USER } from '../redux/actions';
class CrudReducerFactory {
constructor(slice, state = null, initialState = {}) {
state = state || slice;
this.initialState = initialState;
const reducerResult = createSlice({
name: slice,
initialState: initialState[state],
reducers: this._generateReducers(),
extraReducers: (builder) => {
builder.addCase(LOGOUT_USER, (state, action) => {
return { ...this.initialState };
});
},
});
this.reducer = reducerResult.reducer;
this.actions = reducerResult.actions;
}
_generateReducers = () => {
return {
// Create One
requestCreateOne: (state, action) => {
state.isLoading = true;
},
requestCreateOneSuccess: (state, action) => {
state.isLoading = false;
state.one = action.payload;
},
requestCreateOneError: (state, action) => {
state.isLoading = false;
},
// ...snip...
};
};
}
export default CrudReducerFactory;
This is instantiated like so:
const factory = new CrudReducerFactory('users', 'users', { foo: 'bah', one: null, isLoading: false } );
The first argument is the name of the slice, the second is the slice of state and the third is the initial state.
You can then use factory.reducer and factory.actions to use accordingly.
I have a reducer that maintains the currently visible item from a list of some sort, with a case for displaying the next and previous item:
export function currentIndex(state = null, action) {
switch (action.type) {
case types.INCREMENT:
return state + 1
case types.DECREMENT:
return state - 1;
}
}
I also have a random state which is initially false but when set to true I want the currentListItem reducer to be able to account for this and output a a random number instead.
Which is the most idiomatic way of doing this in redux?
The idiomatic solution is to transfer your reducer logic into a thunk using a middleware package such redux-thunk (or similar).
This allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to access the state to conditionally determine the action logic is an excellent use-case for redux-thunk.
Below is a example of how you might pull the logic out of your reducer into a thunk. You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Thunk Example
export const increment= () => {
return (dispatch, getState) => {
const state = getState()
const delta = (state.random) ? getRandomNumber() : 1
dispatch({
type: INCREMENT,
delta
})
}
}
export function currentIndex(state = null, action) {
switch (action.type) {
case types.INCREMENT:
return state + action.delta
}
}
In my case, I have a store like:
{
aa: {...},
bb: cc // the result of computing with aa
}
I need to update aa and bb at the same time, but bb need to get the latest computation of aa.
Here is some code(React.js):
onClick(e) {
const { dispatch, aa, bb } = this.props;
dispatch(updateAa());
dispatch(updateBb(aa)); // can not get the latest computation of aa, it is the last computation..
}
So, is this mean that I need to get aa in bb's reducer?
And How can I do it?
Hope for helps!, Thanks!
don't use combineReducers.
Example
replace this code
export const a = combineReducers({
app,
posts,
intl,
products,
pos,
cats,
});
with
export default (state = {}, action) => {
return {
app: app(state.app, action, state),
posts: posts(state.posts, action, state),
intl: intl(state.intl, action, state),
products: products(state.products, action, state),
pos: pos(state.pos, action, state),
cats: cats(state.cats, action, state),
};
};
reducer would be like
const reducer = (state = initialState, action, root) => {....}
There are several possibilities, but it's tough to say which is best, given the vagueness of the code.
Ideally, your store should be normalized, meaning that each piece of data is only available in one place. Then you would calculate derived data after reading the store, such as when you use the selector pattern described in the guide to map the state of the store to what you might consider a materialized view that will be sent to your components as props. In this workflow, aa and bb would each be produced by selector functions, rather than stored in that store itself.
You could leave the reducer that updates aa and bb outside of combineReducers, so that it sees the whole state, rather than the state scoped down to aa and bb.
You could factor out your calculation code into a helper that could be called by updateAa and updateBb, and pass enough info in each action to make the calculation.
You could calculate the update before dispatching, so that the action contains the right value.
As David L. Walsh said, probably you should structure your reducers in a more logical way.
BUT If you still think you need it, you can use a thunk Middleware.
(https://github.com/gaearon/redux-thunk)
Redux Thunk middleware allows you to write action creators that return a function instead of an action.
Redux Thunk offers you a way to read the current state of the Redux store. In addition to dispatch, it also passes getState as the second argument to the function you return from your thunk action creator.
export function action() {
return function(dispatch, getState){
const state = getState()
dispatch({
type: "ACTION_WITH_SOME_PART_OF_STATE,
some_part_of_state: state.some_part
})
}
}
Ask yourself whether you've structured your reducers correctly. If a and b are not independent of one another, why are they separate reducers? I would try to merge them into a single reducer.
Based on Sheikh Abdul Wahid's answer, I had to do the following modification to make it work with history and connected-react-router:
Notice the () after the connectRouter(history)
import { connectRouter } from 'connected-react-router'
const createRootReducer = (history) => {
return (state = {}, action) => {
return {
...reducers,
router: connectRouter(history)(),
...rest of reducers
}
}
}
If this is a common use case for you, you can try writing your own function to combine reducers according to your needs, as recommended by the official Redux documentation:
Sharing data between slice reducers
Similarly, if sliceReducerA happens to need some data from sliceReducerB's slice of state in order to handle a particular action, or sliceReducerB happens to need the entire state as an argument, combineReducers does not handle that itself. This could be resolved by writing a custom function that knows to pass the needed data as an additional argument in those specific cases, such as:
function combinedReducer(state, action) {
switch (action.type) {
case 'A_TYPICAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
b: sliceReducerB(state.b, action)
}
}
case 'SOME_SPECIAL_ACTION': {
return {
// specifically pass state.b as an additional argument
a: sliceReducerA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
case 'ANOTHER_SPECIAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
// specifically pass the entire state as an additional argument
b: sliceReducerB(state.b, action, state)
}
}
default:
return state
}
}
I highly recommend you to read this documentation page, where there are also other suggestions to share data between reducers, even using combineReducers for simple actions and other custom reducers for the special cases.
I hope these options help!
You can access the other reducer's data in actions and dispatch that data as a param.
actions.js
const actionFn = (param1) => {
return (dispatch, stateFn) => {
const { param2 } = stateFn().other.reducer;
dispatch({
type: ACTION,
param1,
param2,
});
};
};
reducer.js
case ACTION:
return reducerFn(state, data);
const reducerFn = (state, { param1, param2 }) => {
return {
...state,
someState: {
...state.riverhealth,
setParam1: param1
setParam2: param2,
},
};
};
Hope it helps!
If some reducer needs some data from another reducer, a simple solution is to merge them into a single reducer.
In my case, I need some data from another reducer and it is very difficult to manage them so I ended up merging them both.