Action changes state of all values instead of just particular ones - reactjs

I am trying to rewrite my application (weather check application) from standard React state approach into Redux based one. I encountered problem with changing state. In pure React by lanuching this.setState() I was changing only the piece I was passing as an argument in this method. In Redux it seems to change the entire state object even though I pass only part of the entire state, but probably I am doing something wrong.
Here you can see how state transforms when I trigger my action:
In my example I am trying to set coordinates states (lattitude and longtitude) when clicking the button. Tried several things - I thought that perhaps passing state = [] in reducer function clears out the entire state but I cannot get rid of it as application crashes.
I have 3 reducers: first for geolocation coords, second for weather details and last one for handling loading spinner to be displayed or not
const reducers = combineReducers({
weather: weatherReducer,
coords: coordsReducer,
loading: loadingReducer
})
Then I have store with initial states as following:
const store = createStore(reducers, {
weather: [{ temperature: "", humidity: "", windSpeed: "", pressure: "", pollution: "", date: "" }],
coords: { lat: null, lng: null },
loading: { loading: false }
}, allStoreEnhancers);
Not sure if splitting them all into 3 parts is good but I believe so (feel free to point out if I am wrong)
coordsReducer.js:
export default function weatherReducer(state = [], { type, payload }) {
switch (type) {
case 'updateCoords':
return payload
case 'clearCoords':
return payload
default:
return state
}
}
and coordsActions.js (simply set coords or clear them out)
export function updateCoords(lat = 0, lng = 0) {
return {
type: 'updateCoords',
payload: {
lat: lat,
lng: lng
}
}
}
export function clearCoords() {
return {
type: 'clearCoords',
payload: {
lat: null,
lng: null
}
}
}
Basically I need few things:
1. Opinion if the way I splitted my state and action and reducers makes sense
2. How to change state of single properties instead of changing entire state object?

This is because you have to pass in the previous state when you are setting new properties. You can do this easily with the spread operator.
export default function weatherReducer(state = [], { type, payload }) {
switch (type) {
case 'updateCoords':
return {
...state,
lat: payload.lat,
lng: payload. lng
}
case 'clearCoords':
return {
...state,
lat: null,
lng: null
}
default:
return state
}
}
and you can set the values you want to update individually.

Related

Update deeply nested state object in redux without spread operator

I've been breaking my head for a week or something with this !!
My redux state looks similar to this
{
data: {
chunk_1: {
deep: {
message: "Hi"
}
},
chunk_2: {
something: {
something_else: {...}
}
},
... + more
},
meta: {
session: {...},
loading: true (or false)
}
}
I have an array of keys like ["path", "to", "node"] and some data which the last node of my deeply nested state object should be replaced with, in my action.payload.
Clearly I can't use spread operator as shown in the docs (coz, my keys array is dynamic and can change both in values and in length).
I already tried using Immutable.js but in vain.. Here's my code
// Importing modules ------
import {fromJS} from "immutable";
// Initializing State ---------
const InitialState = fromJS({ // Immutable.Map() doesn't work either
data: { ... },
meta: {
session: {
user: {},
},
loading: false,
error: "",
},
});
// Redux Root Reducer ------------
function StoreReducer(state = InitialState, action) {
switch (action.type) {
case START_LOADING:
return state.setIn(["meta"], (x) => {
return { ...x, loading: true };
});
case ADD_DATA: {
const keys = action.payload.keys; // This is a valid array of keys
return state.updateIn(keys, () => action.payload); // setIn doesn't work either
}
}
Error I get..
Uncaught TypeError: state.setIn (or state.updateIn) is not a function
at StoreReducer (reducers.js:33:1)
at k (<anonymous>:2235:16)
at D (<anonymous>:2251:13)
at <anonymous>:2464:20
at Object.dispatch (redux.js:288:1)
at e (<anonymous>:2494:20)
at serializableStateInvariantMiddleware.ts:172:1
at index.js:20:1
at Object.dispatch (immutableStateInvariantMiddleware.ts:258:1)
at Object.dispatch (<anonymous>:3665:80)
What I want ?
The correct way to update my redux state (deeply nested object) with a array containing the keys.
Please note that you are using an incredibly outdated style of Redux. We are not recommending hand-written switch..case reducers or the immutable library since 2019. Instead, you should be using the official Redux Toolkit with createSlice, which allows you to just write mutating logic in your case reducers (and thus also just using any helper library if you want to use one).
Please read Why Redux Toolkit is how to use Redux today.
you could use something like that:
import { merge, set } from 'lodash';
export default createReducer(initialState, {
...
[updateSettingsByPath]: (state, action) => {
const {
payload: { path, value },
} = action;
const newState = merge({}, state);
set(newState, path, value);
return newState; },
...}

ReactJS - Proper way for using immutability-helper in reducer

I have the following object which is my initial state in my reducer:
const INITIAL_STATE = {
campaign_dates: {
dt_start: '',
dt_end: '',
},
campaign_target: {
target_number: '',
gender: '',
age_level: {
age_start: '',
age_end: '',
},
interest_area: [],
geolocation: {},
},
campaign_products: {
survey: {
name: 'Survey',
id_product: 1,
quantity: 0,
price: 125.0,
surveys: {},
},
reward: {
name: 'Reward',
id_product: 2,
quantity: 0,
price: 125.0,
rewards: {},
},
},
}
And my reducer is listening for an action to add a reward to my object of rewards:
case ADD_REWARD:
return {
...state, campaign_products: {
...state.campaign_products,
reward: {
...state.campaign_products.reward,
rewards: {
...state.campaign_products.reward.rewards,
question: action.payload
}
}
}
}
So far so good (despite the fact that every object added is named "question")... its working but its quite messy. I've tried to replace the reducer above using the immutability-helper, to something like this but the newObh is being added to the root of my state
case ADD_REWARD:
const newObj = update(state.campaign_products.reward.rewards, { $merge: action.payload });
return { ...state, newObj }
return { ...state, newObj }
First, you must understand how the object shorthand works. If you're familiar with the syntax before ES2015, the above code translates to:
return Object.assign({}, state, {
newObj: newObj
});
Note how the newObj becomes a key and a value at the same time, which is probably not what you want.
I assume the mentioned immutability-helper is this library: https://www.npmjs.com/package/immutability-helper. Given the documentation, it returns a copy of the state with updated property based on the second argument.
You're using it on a deep property so that it will return a new value for that deep property. Therefore you still have to merge it in the state, so you have to keep the approach you've labelled as messy.
What you want instead is something like:
const nextState = update(state, {
$merge: {
campaign_products: {
reward: {
rewards: action.payload
}
}
}
});
return nextState;
Note how the first argument is the current state object, and $merge object is a whole object structure where you want to update the property. The return value of update is state with updated values based on the second argument, i.e. the next state.
Side note: Working with deep state structure is difficult, as you've discovered. I suggest you look into normalizing the state shape. If applicable, you can also split the reducers into sub-trees which are responsible only for the part of the state, so the state updates are smaller.

Updating property of nested object in Redux using action

Inside my reducer, my initial state has a structure like the following:
const initialState = {
showOverlay: false,
chosenAnimal: null,
sliderValue: 0,
colorValues: {
a: null,
c: null,
g: null,
t: null,
bg: null
}
};
I'm attempting to update one of the colorValues properties based on an action.type.
return {
...state,
colorValues: {...state.colorValues, action.basePair: action.colorHex}
};
action.basePair is giving me a parsing error which makes it seem as if I cannot set the property name dynamically using action. If I change action.basePair to gfor example, then the expected behavior occurs and the value of the property g is indeed updated as expected.
But is there anyway to do this so that I can set the property name dynamically through action? Thank you.
edit: The code of my action is the following:
const mapDispatchToProps = dispatch => {
return {
onSetColorValue: (basePair, colorHex) =>
dispatch({ type: "SET_COLOR_VALUES", basePair: basePair, colorHex: colorHex })
};
};
action.basePair is giving me a parsing error which makes it seem as if
I cannot set the property name dynamically using action.
Wrap action.basePair to [action.basePair]
return {
...state,
colorValues: {...state.colorValues, [action.basePair]: action.colorHex}
};
Also, it is a good practice to use payload key for action params
dispatch({ type: "SET_COLOR_VALUES", payload: { basePair, colorHex })
Reducer
return {
...state,
colorValues: {
...state.colorValues, [action.payload.basePair]: action.payload.colorHex
}
};
when using dynamic value as key, we can use the square brackets notation. Please see below:
return {
...state,
colorValues: {
...state.colorValues,
[action.basePair]: action.colorHex
}
};
You can visit below link for more details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors

React JS - How to set state of variable inside variable?

I want to set state of this form :
this.state = {
filter: {
search: null,
brands: null,
price: null
}
}
How to set value for search / brands / price ?
Do the following:
this.setState({
filter: {
search: 'value',
brands: 'value',
price: 'value'
}
})
The key is that you don't want to ever mutate a value in state. As a result, you must copy the filter object before passing it to setState. Example:
onSearchChange(value) {
this.setState((state) => {
return {
filter: {
...state.filter,
search: value
}
})
}
Note that I am passing a function to setState. Since the next value of state relies on the previous value, you want to use an updater functions, as the setState docs recommend.
In general, it is nicer if you can keep your state flat. So rather than having a filter object in state, your shape could just be
this.state = {
search: null,
brands: null,
price: null,
}
In which case the above onSearchChange function would just be
onSearchChange(value) {
this.setState({search: value})
}
Definitely a lot easier on the eyes.
I recommend avoiding nested objects and keeping your state flat. e.g.
this.state = {
brandsFilter: null,
priceFilter: null,
searchFilter: null,
};
Component state in react is really nothing more than simple key-value pairs; that's what setState supports. Sure you can have nested objects if you really want. But as the other answers demonstrate, supporting such an approach can be needlessly complex.
you should use the setState function, you can set the filter with updated data like so
const newFilter = {...};
this.setState({filter: newFilter});
You should avoid to mutate React state directly, there are some functions can do immutable jobs for you (ex Object.assign):
const currentFilter = this.state.filter;
const newFilter = Object.assign({}, currentFilter, {search: "new search", brands: [], price: 100});
this.setState({filter: newFilter});
ES 6:
const currentFilter = this.state.filter;
this.setState({
filter: {
...currentFilter,
search: "new search",
brands: []
}
});
this.setState({
filter: {
...this.state.filter,
search: "new search",
brands: 'value',
price: 'value'
}
});
You may try this with Spread Operator.
If you need to preserve the previous filter value.
other wise you can,
this.setState({
filter: {
search: "new search",
brands: 'value',
price: 'value'
}
});
let newfilter = Object.assign({}, this.state.filter)
newfilter.search ="value";
newfilter.brands ="value";
newfilter.price ="value";
this.setState({
filter:newfilter
})
You can access the search, brands, price by using:
this.setState({
filter.search = true,
filter.brands = 'stackoverflow',
filter.price = 1400
})
and to access it, just like usual state access (this.state.filter.search).

React Native redux reducers best way to push array

I have the following state and reducer but it's not pushing in the new array object.
const initialState = {
photos: [],
selectedPhoto:{},
photosTeamId:'',
photosProjectId:''
};
case actionTypes.PHOTOS_UPDATE:
return Object.assign({}, state, {
photos:action.data.photos,
photosTeamId:action.data.photosTeamId,
photosProjectId:action.data.photosProjectId
})
photos is not getting pushed but overwritten
Here's a more cleaner way using javascript spread syntax:
const initialState = {
photos: [],
selectedPhoto:{},
photosTeamId:'',
photosProjectId:''
};
case actionTypes.PHOTOS_UPDATE:
return {
...state,
photos: [...state.photos, ...actions.data.photos],
photosTeamId: action.data.photosTeamId,
photosProjectId: action.data.photosProjectId
}
case actionTypes.PHOTOS_UPDATE:
return {
...state,
photos: state.photos.concat(action.data.photos),
photosTeamId: action.data.photosTeamId,
photosProjectId: action.data.photosProjectId
};
Here's the spread operator […]. The spread operator can be used to take an existing array and add another element to it while still preserving the original array.
Example:
case actionTypes.PHOTOS_UPDATE:
return [
...state,
Object.assign({}, {
photos:action.data.photos,
photosTeamId:action.data.photosTeamId,
photosProjectId:action.data.photosProjectId
})
];

Resources