I'm a beginner in React & Redux and I am confused with manipulating the state in Reducers.
In most of the articles, documentations, I keep seeing that the states are immutable and we should never update the state. We should always use ...state or object.assign in the reducers
However, in famous tutorials (Cory House or other places (Eg. Here on GitHub) , they update the state the directly like the following:
var initialState = {
numberOfAjaxCall: 0
}
const ajaxStatusReducer = (state = initialState.numberOfAjaxCall, action) => {
if (action.type === AJAX_BEGIN_CALL) {
return state + 1;
}
return state;
}
Why these codes are not written like the following?
var initialState = {
numberOfAjaxCall: 0
}
const ajaxStatusReducer = (state = initialState, action) => {
if (action.type === AJAX_BEGIN_CALL) {
return {
...state,
numberOfAjaxCall: state.numberOfAjaxCall + 1
};
}
return state;
}
I would like to know whether my code is wrong or not. Or I misunderstood about Redux & Reducers or don't understand the way these codes are implemented?
Could you please help me to enlighten about these coding styles?
The first example doesn't mutate state - it returns a new number. In that case the reducer is responsible for only the one number.
If your state is shaped like in the example you gave:
var initialState = {
numberOfAjaxCall: 0
}
The reducer ajaxStatusReducer is responsible for numberOfAjaxCall only. You will still need another reducer for the overall state object, which could look something like this (simplest option, lots of other ways you could write this):
function reducer(state, action) {
return {
numberOfAjaxCall: ajaxStatusReducer(state.numberOfAjaxCall, action)
};
}
In the second example, you combine both of these reducers into one. Both are valid options and it depends on how you like to compose your code/reducers in general.
Related
I have seen 3 forms of reducers:
// Form 1
function reducer(state, action) {
if (state === undefined)
return state = [];
// handle action.type
// ...
}
// Form 2
function reducer(state, action) {
if (state === undefined)
state = [];
// handle action.type
// ...
}
// Form 3
function reducer(state = [], action) {
// handle action.type
// ...
}
Are they all the same? Form 1 and Form 2 differ by Form 1 immediately returning the initial state without looking at and taking care of action.type at all.
And I think Form 2 and Form 3 are exactly the same, using default parameter value.
Can any claim be substantiated by any official docs or specs? It think it means, the very first time a reducer is called, action.type won't be anything meaningful.
In a Redux reducer, if state is not defined, can you immediately return the initial state?
yes, we can.
But for this, you don't need to check for undefined or any other non-empty check.
switch default statement will handled it very smoothly.
function reducer(state, action) {
switch(action.type){
//rest case
default:
return state;
}
}
// Form 2
function reducer(state, action) {
switch(action.type){
//rest case
default:
return state;
}
}
Redux initializes it dispatches a "dummy" action to fill the state. Please check this docs
Reducers are not allowed to return undefined under any condition, It can return null if necessary
Technically all three are same, as first time the reducers will called with dummy, state is no longer undefined at Form 1. On subsequent call, it will be called with meaningful action from your application code and as state = [] at that time, it will check the action.type
You may simply use: createReducer from redux-starter-kit
Which is also been used in this demo from microsoft
A utility function that allows defining a reducer as a mapping from action type to case reducer functions that handle these action types. The reducer's initial state is passed as the first argument.
The body of every case reducer is implicitly wrapped with a call to produce() from the immer library. This means that rather than returning a new state object, you can also mutate the passed-in state object directly; these mutations will then be automatically and efficiently translated into copies, giving you both convenience and immutability.
#param initialState — The initial state to be returned by the reducer.
#param actionsMap — A mapping from action types to action-type-specific case redeucers.
Usage
export const LocalStorageReducer = createReducer<Store['localStorage']>(
new LocalStorage(), // <= where you define the init value of this state
{
storeLocalStorageInput(state, action) {
return state = {...state, [action.payload.item]: action.payload.value};
},
clearLocalStorageInput(state, action) {
return state = new LocalStorage();
},
}
);
export const reducer = combineReducers({
localStorage: LocalStorageReducer,
...
type of createReducer
(alias) createReducer<LocalStorage, CaseReducers<LocalStorage, any>>(initialState: LocalStorage, actionsMap: CaseReducers<LocalStorage, any>): Reducer<LocalStorage, AnyAction>
import createReducer
sample of state
export class LocalStorage {
/**
* Editing plan id in planner pages
*/
planId: string | undefined;
/***
* Touched id list
*/
touchedIdList: Array<string>;
constructor() {
this.planId = undefined;
this.touchedIdList = Array<string>();
}
}
There are already developed methods to do those things by libs, no need to manually do it again in most situation.
Please correct me if I'm wrong, because I've heard different stories with using redux with react. I've heard that you should have all your logic in your reducers, I've also heard that the store should be your single source of truth.
That said, I'm taking the approach where my logic, that is filtering stuff from json file is in a reducer file. Then I call the actions to filter out different parts of the json and return it,
import Data from "./BookOfBusiness.json";
const initialState = {
TotalData: Data
};
const filterDataWTF = (state = initialState, action) => {
let newState = { ...state };
let itExport = newState.TotalData;
if (action.type === "Status55") {
let itExport22 = {...itExport};
console.log("came to Status55 and itExport value is " + itExport22); // comes undefined. WHY??
return itExport22.filter(xyz => {
if (xyz.Status55.includes("Submitted")) {
return true;
}
return false;
});
}
return itExport;
};
export default filterDataWTF;
the problem is my variable itExport22 is showing up as undefined. Any tips please. TYVM
Okay once we got something working that you can vote for an answer, lets write an answer for you.
In that case your filter is returning a boolean. You should do something like so:
return itExport22.filter(xyz => xyz.Status55.includes("Submitted"));
Returning an empty array you are sure that you are receiving the same type on your component.
Your filter is fine. Only this should work
Your reducer should always return the new state
your reducer return type is boolean or itExport22 but it's should be object contains TotalData
Have a small problem with fetching and based on response updating an array inside my state in Redux.
First I have done the whole array update with forEach in actions (based on my initial state object) and sent it ready to reducer, it worked. Simple.
But then read tutorials that modifying should be done only in the reducer, and that action should only deal with getting the response. So I have tried doing it this way, two ways, both failed.
The payload i have dispatched to reducer in both cases was just the ready response i have got.
Can someone please enlighten me what went wrong and what's the correct way to do this in reducer?
Both approaches didn't work:
export const handleMusicCards = (state = musicState, action = {}) => {
switch (action.type) {
case REQUEST_MUSIC_SUCCESS:
return Object.assign({}, state, {
musicStateItemList: state.musicStateItemList
.forEach((el, i) => {
el.track = action.payload.message.body.track_list[i].track.track_name;
el.album = action.payload.body.track_list[i].track.album_name;
el.artist = action.payload.body.track_list[i].track.artist_name;
el.id = action.payload.body.track_list[i].track.track_id;
el.favClicked = false;
el.addedToFav = false;
}),
isLoading: false
});
}
}
export const handleMusicCards = (state = musicState, action = {}) => {
switch (action.type) {
case REQUEST_MUSIC_SUCCESS:
return Object.assign({}, state, {
musicStateItemList: state.musicStateItemList
.forEach((el, i) => {
return {
...el,
track: action.payload.message.body.track_list[i].track.track_name,
album: action.payload.message.body.track_list[i].track.album_name,
artist: action.payload.message.body.track_list[i].track.artist_name,
id: action.payload.message.body.track_list[i].track.track_id,
favClicked: false,
addedToFav: false,
}
}),
isLoading: false
});
}
}
I am not sure after reading it where the failure is occurring. A little more about redux conventions.
The action objects are only to describe what changes should be made to the state. The reducer is where the state should actually be changed. In redux, you never want to modify the state object, instead you want to copy it and return a new object with the changes, as described by the action objects.
So you might have a reducer case that looks something like this...
const reducer = (state, action) => {
switch (action.type) {
case NEW_RECORD_SUBMIT :
return {
...state,
newRecordStatus: action.status,
};
default :
return state;
}
};
It's solved now. Very silly mistake, wrong case in switch statement...Went for the second option I tried, with map()
I am struggling a bit with Redux.
While I have managed to move some very simple states to Redux based on tutorials, I find it difficult to deal with more complex ones. Tried researching but answers vary and confuse.
The way I understand it, the equivalent of setState lies in Redux's actions and should be achieved by using getState() and then bt dispatch()ing the action to reducer...
If yes, then how to correctly translate the below example into Redux?
Let's say toggling a boolean of some nested music state element:
this.setState(prevState => {
const updatedMusic = prevState.music;
const elToUpdate = updatedMusic.musicStateItemList[3].favClicked;
elToUpdate = !elToUpdate;
return {
music: updatedMusic
};
});
the equivalent with your sample code in Redux is as below
const updatedMusic = state.music;
const elToUpdate = updatedMusic.musicStateItemList[3].favClicked;
updatedMusic.musicStateItemList[3].favClicked = !elToUpdate;
return {
...state,
music: [...updatedMusic]
};
it is about mutating state. You can use immutability helper if you want more advanced.
In the reducer the state is passed
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
let store = createStore(counter)
From the docs: https://redux.js.org/introduction/getting-started
I am trying to wrap my head around redux and sagas and I think I have set something up wrong and i'm hoping someone can lend some insight.
I have created my store with my inital state and I dispatch an action, as seen here:
const initialState = fromJS({
product: {},
basket: {},
global: {}
});
const reducers = combineReducers({ product, basket, global });
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducers,
initialState,
applyMiddleware(sagaMiddleware))
initSagas(sagaMiddleware);
store.dispatch(retrieveSiteKeyValues())
return store;
};
Combine Reducers is from redux-immutable.
My saga function:
export function* fetchSiteKeyValuesSaga() {
yield take(RETRIEVE_SITE_KEY_VALUES)
const siteKeyValues = yield call(retrieveSiteKeyValues)
yield put(storeSiteKeyValues(siteKeyValues));
}
My reducer function:
const storeSiteKeyValues = (state, payload) => {
payload.anotherObject = {};
payload.anotherMap = new Map();
const newState = fromJS({ payload })
return newState
// OR return state.merge({ global: { siteKey: action.siteKey } }); ?
}
When I interrogate the state object the size is zero. I expected the size to be at least 3 due to my initalState. When the newState is 'created' the size is 4. But when it drops back into the state switch statement, the state size is zero again:
export default (state, action) => {
switch (action.type) {
case STORE_SITE_KEY_VALUES : {
return storeSiteKeyValues (state, action.payload);
}
default:
return state;
}
}
Im 90% sure just dumping over the state as I am doing in the reducer function is wrong and i should be using set() or setIn() i thought update() would make more sense, but when I use those methods the state is always empty or 'undefined' if I try to enact .get(x) in the console.
When I inspect the state in the browser it looks like this:
storeState:{
[2],
[2]
[2]
}
The array expanded looks like this:
0:"product"
1:{anotherObject :{}, anotherMap: map()
size:1
I would expect the values that were part of of the payload to be here not just the new object and map.
Am I initaiting my state incorrectly at the store creation? Am I approaching redux and state management in the wrong way?
I want to be sure you aren't missing a fundamental part: where is the sagaMiddleware.run(YOUR_SAGA); call? Is it hidden inside initSagas?
It was setting my inital state twice, once when I was initialsing my store and again when the reducer inital state was read. My state in my reducer was an empty object as it would be if on the time of reducer 'activation'. In the end I realised i'm not reading some 'remembered' state from anywhere, I just needed some inital values. Which I moved into the reducer and remvoed the immutable js from my app as it was confusing matters.
Some lessons for all you newbies to react/redux-saga! Don't try and over complicate matters. Learn what immutable mean! Figure out for yourself if you need it, in my case just having one source of truth and access to state was enough.
Further reading:
Initalising State ,
Immutable and State considerations