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;
Related
My state has an array of objects, where every time the action RECIPE_UPDATED is triggered, a new object is added to the array. The very first item of the array is generated by the action FIRST_RECIPE. See the actions and reducers bellow.
The issue happens that every time the action RECIPE_UPDATED is called, it adds the new item to the array but it also changes the last item that was in the array before and so far I couldn't understand why it behaves as such.
These are the action creators:
export const getFirstRecipe = (recipe) => ({
type: "FIRST_RECIPE",
payload: recipe,
});
export const updateRecipe = (data) => ({
type: "UPDATED_RECIPE",
payload: data,
});
An these are the reducers:
const recipeHistoryReducer = (state = [], action) => {
switch (action.type) {
case "FIRST_RECIPE":
return [action.payload];
case "UPDATED_RECIPE":
return [...state, action.payload];
default:
return state;
}
};
export default recipeHistoryReducer;
Any ideas how to solve this issue?
issue solved.
It was not related to the reducers or actions at all. The whole issue was a mistake where I was assigning the object with the values into a new variable without using the spread operator, so the whole thing was being changed at once.
Problem solved.
Jorge
My apologies for the title.
Anyway, I'm seeing strange behavior in my Redux Dev Tool when I create a certain type of action. The only way to show what I mean is via an example.
Let's say I have a state with this structure:
const INITIAL_STATE = {
isFile: false,
isUploaded: false,
isUser: true
};
Since there are simply keys with no nested objects inside the state, I figure I can just create one action to change all of the keys instead of a different action for each particular key.
So the reducer becomes:
const CHANGE_KEY = "CHANGE_KEY";
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CHANGE_KEY:
return {
...state,
[action.payload.key]: action.payload.value,
};
default:
return state;
}
};
const changeKeyInState = (key, value) => ({ type: CHANGE_KEY, payload: { key, value } });
Now all of this works. So I can call one action to change simple key-value pairs in the state. However, let's say I have different reducers that handle different areas of the state with their own key changing action. When I look at Redux Dev tools, it shows keys added to states that shouldn't be there, which doesn't make sense. For example, if I had a reducer for a logon that has an action that changes isLoggedIn key, that key is also added to the above reducer, which doesn't make any sense.
Here is the code for the different reducer:
const CHANGE_LOGIN_KEY = "CHANGE_LOGIN_KEY";
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CHANGE_LOGIN_KEY:
return {
...state,
[action.payload.key]: action.payload.value,
};
default:
return state;
}
};
const changeKeyInLoginState = (key, value) => ({ type: CHANGE_LOGIN_KEY, payload: { key, value } });
I only see this in Redux Dev tools. I don't know what's going on. Can anyone help me figure out what the issue is?
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 using Redux to manage the state of my react app. I am creating an object, then passing this object to addTile function which is my action.
So my action.ts looks like this:
export function addTile(tile){
return {
type: "ADD_TILE",
payload: tile
}
}
My reducer.ts looks like this:
const reducer = (state = {
isPanelOpen: false,
isDiscardAllChangesOpen: false,
tiles: [],
tempTiles: [],
}, action) => {
switch (action.type) {
case "PANEL_VISIBILITY":
state = {
...state,
isPanelOpen: action.payload
};
break;
case "ADD_TILE":
state = {
...state,
tiles: [...state.tiles, action.payload]
}
break;
}
return state;
};
export default reducer;
However, if I try to use this in my component like this:
this.props.addTile(tile)
alert(this.props.tiles.length)
The length will be 0. However, the item is really added to the array, but at the time of the alert execution, the length was 0. From my reading on Redux docs, actions by default are async (or at least that's how I understand they are).
I even try to do this:
this.props.addTile(tile)
.then(response => { //some code})
Then I get cannot read property then of undefined.
Any ideas?
When you try to check the prop right after dispatching an action, React has not yet had a chance to re-render. It's not that it's "taking too long", it's that your own code is still executing. So, React has not re-rendered your component, and the prop value is still the same.
Your promise example would only work if addTile() was a thunk that returned a promise.
I have a reducer whereby I am retuning the appropriate state when an action is dispatched. Now I am calling an API at regular intervals so the result will trigger an action again and again. So what I want is that if the reducer state already has data then another reducer doesn't show the state as loading while the call is sent. It must maintain its loading state when receiving data the first time only. I hope I am able to explain it properly
Here are my code snippets
Loading state reducer
const loading = (state = false, action) => {
switch (action.type) {
case 'GET_AUDIT_DATA': // here I want to return true only when there is no data available
return true
case 'GET_AUDIT_DATA_RECEIVED':
return false
case 'GET_AUDIT_DATA_ERROR':
return false
default:
return state
}
}
Combining reducers
const allReducers = combineReducers({
auditData: AuditData,
auditLoading: AuditLoading,
modifiedOrders: ModifiedOrders
});
export default allReducers;
Reducer returning data on action triggered by superagent
const auditData = (state = [], action) => {
switch(action.type) {
case 'GET_AUDIT_DATA_RECEIVED':
console.log(action.data);
return action.data;
case 'GET_AUDIT_DATA_ERROR':
return action.err;
default :
return state;
}
}
export default auditData;
So initially the auditData doesn't contain any data, only after the first success call it returns the data. When this is called at the same time loading state reducer is called and it should return true in GET_AUDIT_DATA action only when the audit data reducer doesn't contain any data.
Also is returning just the current obtained data from auditData the right way to go or I should do it differently. Basically I want to overwrite the current data with the new one.
You can call getState() over a store to get the list of reducers and the current state inside the reducers.
Import the store into auditLoading (use store to get values. Don't mutate the store)
store.getState().auditLoading will give you the state of auditLoading reducer.
This approach is similar to the callback provided by redux-thunk. In which (dispatch, getState) => {} will be returned to the action.
The best way to proceed is to send to Loading state reducer an information to know if the other reducer already have data. To have at the end:
const loading = (state = false, action) => {
switch (action.type) {
case 'GET_AUDIT_DATA':
if(!action.dataAlreadyInitialized){
return true
}
case 'GET_AUDIT_DATA_RECEIVED':
return false
case 'GET_AUDIT_DATA_ERROR':
return false
default:
return state
}
}
You should have access from your action function to the application state and do:
dispatch({
type:'GET_AUDIT_DATA',
dataAlreadyInitialized: appState.auditData.length > 0
});
The accepted answer is fine (pass in the data length through the action) but can get laborious if it's a piece of information that is widely used. There is another solution that is sometimes preferable for something like 'current user' that might be used by every action.
According to the Redux FAQ https://redux.js.org/faq/reducers it is perfectly acceptable to add a third argument to the reducer function. I.e.:
Loading state reducer
const loading = (state = false, action, noData) => {
switch (action.type) {
case 'GET_AUDIT_DATA':
return noData
case 'GET_AUDIT_DATA_RECEIVED':
return false
case 'GET_AUDIT_DATA_ERROR':
return false
default:
return state
}
}
Combining reducers
Unfortunately it means we have to write code to combine the reducers, rather than use the combineReducers shortcut. But it's not too hard, you just call each reducer and create a new object if anything changed:
const allReducers = (state = null, action) => {
const auditData = AuditData(state?.auditData, action);
const auditLoading = AuditLoading(state?.auditLoading, action, !state?.auditData?.length);
const modifiedOrders = ModifiedOrders(state?.modifiedOrders, action);
return (auditData !== state?.auditData ||
auditLoading !== state?.auditLoading ||
modifiedOrders !== state?.modifiedOrders) ?
{ auditData, auditLoading, modifiedOrders } : state;
});
export default allReducers;
Notice the third argument passed to the AuditLoading reducer. No change is required to the other reducers, or to the code that invokes the action. Which is nice!
import store from '../../../redux/store'
console.log(store.getState().loginReducer.accessToken)
Through this statement we will get state of accessToken