Changing state in Redux based on previous state - reactjs

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

Related

Redux reducer - modifying array with forEach

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()

Redux state is not updating into the react component

How the redux state is updated into the react component?
I tried using initial state and not mutating the object and return that
Redux
const initialState = {
filteredProviderData:[],
filteredAlsoSpeaksData:[],
filteredOfficeHours:[]
};
function reducer(state = initialState, action = {}) {
switch (action.type) {
case HANDLE_FILTER_CHANGE:
let filteredProviderData = '';
let filteredAlsoSpeaksData='';
let filteredOfficeHours = ''
return {...state, filteredProviderData,filteredAlsoSpeaksData,filteredOfficeHours};
case RESET_FILTER_COLLECTION:
// RESET the Array as shown into the HANDLE_FILTER_CHANGE
}}
React component
const mapStateToProps = state => {
return {
filteredProviderData:state.providerList && state.providerList.filteredProviderData,
filteredAlsoSpeaksData:state.providerList && state.providerList.filteredAlsoSpeaksData,
filteredOfficeHours:state.providerList && state.providerList.filteredOfficeHours
}}
Here my question is how to update the array which is into the HANDLE_FILTER_CHANGE
RESET_FILTER_COLLECTION
I need to update array based on some condition and return that updated array
Here into the based on the condition only one array will be updated and return that only two would remains same.
You need to change the local state once you updated the redux store. this.setState({loading:true}) Kind of approach will work and it works for me too.

Do we always have to treat states as immutable in Reducer?

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.

Redux-Saga using Immutable.js and state is always empty

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

React-redux - state overwrites itself

I am using react-redux (for the first time). I have a component into which users put a 'startDate' and an 'endDate'. These should then be stored in the redux store, so that they persist.
I have the following action creator:
export const setDates = dates => ({
type: "SET_DATES",
payload: dates
});
The following reducer:
const dates = (state = {}, action) => {
switch (action.type) {
case "SET_DATES":
return action.payload;
default:
return state;
}
};
export default dates;
The state is set conditionally (i.e. only if the start and end dates actually make sense) like this:
handleSubmit = () => {
if (this.state.startDate <= this.state.endDate) {
store.dispatch(setDates([this.state.startDate, this.state.endDate]));
window.location = `/search/${
this.state.location
}&${this.state.startDate.format("DDMMYYYY")}&${this.state.endDate.format(
"DDMMYYYY"
)}&${this.state.guestCount}&${this.state.offset}&${this.state.count}`;
} else {
console.log("HANDLE ERROR");
}
};
The problem, according to the chrome redux dev-tools, is that when the submit is triggered, the store does indeed change to the new dates, but it then seems to be immediately overwritten to the empty state. By modifying the reducer to take state = {dates: 'foo'} as its first argument, I can get the store to persist 'dates:foo'. This suggests to me that, for some reason, the reducer is being called twice - once with an action of type "SET_DATES", which works, and then again, immediately, with an action of unknown type (confirmed by console.log-ging action.type), which causes it to return the default state.
So I'm pretty sure I know what the problem is, but I have no idea why it would do this.
I Already commented, but anyways. The problem is that you reload the page. It reloads redux, and it boots up from initial state, which is probably an empty array. Here is a great video from one of the brains behind redux.
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
It all boils down to subscribing to the store state changes, and saving it / loading the state back from storage of your choise.
Try changing you reducer like this
const dates = (state = {}, action) => {
switch (action.type) {
case "SET_DATES":
return Object.assign({}, state, {
action.payload
});
default:
return state;
}
};
export default dates;

Resources