Redux reducer - modifying array with forEach - reactjs

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

Related

I can't figure out how to use visibilityFilters in react redux todo app

I have a todo app that does all 4 crud operations but I can't filter them based on their current status here's the app on codesandbox.
import { SET_VISIBILITY_FILTER } from "../actionTypes";
const initialState = {
filters: ["SHOW_ALL"]
};
const visibilityFilter = (state = initialState, { type, payload }) => {
switch (type) {
case SET_VISIBILITY_FILTER:
return { payload };
default:
return state;
}
};
export default visibilityFilter;
Any explanations will be appreciated.
I have also checked other react redux todo app github repos but most of them are old and it didn't look like they were writing in the best possible way, so I am trying to find a better way (and so far failing at it)
A few issues
filters is an array in the initial state, but you send single values there after in your action, and you also use it a single value when filtering with it.
you expect payload in your reducer but the data you dispatch does not wrap things in payload
dispatch({
type: SET_VISIBILITY_FILTER,
filter
});
in continuation to the above you should use the already defined action setFilter for setting a filter, which correctly wrap the data in a payload property.
fixing these 3 issues, you get https://codesandbox.io/s/problems-with-redux-forked-hv36h which is working as intended.
What you are doing is an anti-pattern when you mutate the redux state variable inside the component like this:
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case "SHOW_ALL":
return todos;
case "SHOW_COMPLETED":
return todos.filter((t) => t.completed);
case "SHOW_ACTIVE":
return todos.filter((t) => !t.completed);
default:
return todos;
}
};
Instead what you should do, listen to the SET_VISIBILITY_FILTER action on toDoReducer.js:
//import SET_VISIBILITY_FILTER action
case SET_VISIBILITY_FILTER:
let toDoClone = [...state.todos]
//if(filter = something)
toDoClone.filter(t => //your condition)
return {
...state,
todos: toDoClone
}

Strange behavior with Redux action and reducer when creating action to change keys in state

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?

How to update multiple state properties with immer.js

I wonder if it is possible to update multiple properties of state with immer.js in one "call".
Say I have state:
export const initialState = {
isUserLogged: false,
menuIsClosed: false,
mobileMenuIsClosed: true,
dataArray: ["dog","cat"],
};
And action creator:
export function updateSearchPage(data) {
return {
type: UPDATE_SEARCH_PAGE,
payload: data
};
}
I then use that action creator in React component like this:
this.props.updateSearchPage({
isUserLogged: true,
menuIsClosed: true,
dataArray: ["dog","cat","owl"]
})
The idea is that I want to update several properties of state at the same time. But I dont know which properties it is in advance. I know how to do it with a simple reducer:
case UPDATE_SEARCH_PAGE:
return Object.assign({}, state, action.payload)
But how to update several properties of state with immer at the same time? When the state properties (which one should update) are unknown in advance.
You can cycle on action.payload like the following:
const yourReducer = (state, action) =>
produce(state, draft => {
switch (action.type) {
case UPDATE_SEARCH_PAGE:
Object.entries(action.payload).forEach(([k, v]) => {
draft[k] = v;
})
break;
// ... other
}
}
Also: remember that on recent versions of immer is perfectly legit to returns an object, so doing return Object.assign({}, state, action.payload) is still valid inside a produce call.
With ES6 you can do it this way:
export const state = produce((draft, action) => {
switch (type) {
case UPDATE_SEARCH_PAGE:
return {...draft, ...action.payload}
}
}, initialState)
In this case it works the same way as without Immer. All properties will be merged (shallow merge) into state. If you need to replace the state just return action.payload
Immer gives you a draft state that you can edit. Behind the scenes it uses ES6 proxies to discover what you changed and apply in an immutable way your edits to the original state.
Basically, you can do the exact same thing you do right now, but using the Immer api:
import produce from 'immer'
const newState = produce(this.state, draft => Object.assign({}, draft, payload))
If you, instead, know what properties are changed, you can do something like:
const newState = produce(this.state, draft => {
draft.propertyOne = 'newValue'
draft.propertyTwo = 42
})

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;

Why is my Redux State nested ?

I am writing my first bigger React/Redux/Meteor App. I know that Redux is not necessarily needed in an Meteor App, but I want to use it.
I load a record from a MongoDB with Meteor and then I want to store this object in my Redux store. But the object gets nested in the store and I do not know why this is the case.
Here is my code so far:
Action loads the remote record
export const loadRecord = (id) => {
return dispatch => {
Meteor.call('loadRecord', id, (error, result) => {
if (!error) {
dispatch({
type: TYPE,
result
});
} else {
dispatch({
type: TYPE_ERROR,
error,
});
}
});
};
};
Reducer should update my store
const initialState = {
singleRecord: {}
};
export function singleRecord(state = initialState, action) {
switch (action.type) {
case TYPE:
return {
...state,
singleRecord: action.result
};
default:
return state;
}
}
In more store I expect something like this:
singleRecord: {
id:"1223",
text:"abcde"
}
But what I get is:
singleRecord: {
singleRecord {
id:"1223",
text:"abcde"
}
}
So my store gets updated and everything is working as expected beside the fact, that my record is nested somehow.
I think I am missing a fundamental thing, or I implemented it wrong. It would be very nice if someone can explain me if this is the intended behavior or if not, can tell me why my code is not working as intended.
Thanks in advance
You want to unwrap the payload of the action:
return {
...state,
...action.result
};
or, in other words:
return Object.assign({}, state, action.result);
I am not sure what else you want to save in singleRecord but it's entirely possible you want to do this:
return action.result;
Also, your initial state should be just const initialState = {};
The object returned from your singleRecord reducer is what is stored into singleRecord state.

Resources