Why is my Redux State nested ? - reactjs

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.

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
}

Filtering data in a reducer then attaching it to a component with { connect } from 'react-redux';

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

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

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;

Setting redux state doesn't work after a hard refresh

I am setting my redux state through a value I have in localStorage. This works fine when I navigate into my page. However, when I do a hard refresh the state is never set, despite the value in localStorage being passed down.
This is what my code looks like:
class SomeComponent {
componentWillMount() {
if (typeof localStorage !== 'undefined') {
console.log('I get to here...', localStorage.getItem('someValue')) // this comes in as expected always
this.props.setMyReduxState(localStorage.getItem('someValue'))
}
}
render () {
// will have the value of the localStorage item someValue when navigated into the page
// will be an empty string if I do a hard refresh
console.log('this.props.myReduxState', this.props.myReduxState)
return (
<div>
Stuff...
</div>
)
}
}
const mapStateToProps = (state) => {
return {
myReduxState: state.something.myReduxState || ''
}
}
const mapDispatchToProps = (dispatch) => {
return {
setMyReduxState (someValue) {
dispatch(setMyReduxState(someValue))
}
}
}
Any ideas?
Edit: just a small addition to simplify the problem: I also tried it sending a string directly to setMyReduxState function, without the localStorage, the state still isn't being set. So basically something like this:
componentWillMount() {
this.props.setMyReduxState('some string!')
}
From my understanding every time the redux state is set, the component should re-draw, which isn't happening when there is a hard refresh. Are there any reasons for this or something being done incorrectly?
Edit2: Including my action creator and reducer in case needed:
const SET_MY_REDUX_STRING = 'admin/users/SET_MY_REDUX_STRING'
const defaultState = {
myReduxState: ''
}
export function setMyReduxState (value) {
return {
type: SET_MY_REDUX_STRING,
myReduxState: value
}
}
export default function reducer (state = defaultState, action = {}) {
switch (action.type) {
case SET_MY_REDUX_STRING:
return Object.assign({}, state, { myReduxState: action.myReduxState })
default:
return state
}
}
Some of the checklist you need to follow while using redux -
Are you dispatching an action creator that returns an object with 'type' and data? Here 'type' is mandatory.
Does your reducer return the state with the updated data that it received?
Make sure you do not mutate the state in reducer. Always use {...state, someKey: someValue}

Resources