Update specific state in Redux with reducer - javascript

I would like to know, how can I update a specific value in state. I have my state config, and I would like to update the 'offset' value without modify the limit or total value, just set a new offset with this.props.setParams()...
With this function, I would like to modify offset this.props.setParams({ offset: 10}), or limit this.props.setParams({ limit: 40}), etc... just send a params and modify directly the associate state.
searchList.js
this.props.setParams({ offset: 10})
actions-search.js
export function setFilters(newFilters) {
return {
type : SEARCH.SET_NEW_FILTERS,
payload: newFilters
};
}
export function setParams(newParams) {
return {
type : SEARCH.SET_NEW_PARAMS,
payload: newParams
};
}
reducer-search.js
...
/*State : CONFIG*/
/*config: {
filters : {
...
}
params: {
offset: 0,
limit: 25,
total: 100
}
}*/
case SEARCH.SET_NEW_FILTERS :
let newFilters = action.payload;
return {
...state,
config: {
...state.config,
filters : {
...state.config.filters,
newFilters
}
params : {
...state.config.params
}
}
};
case SEARCH.SET_NEW_PARAMS :
let newParams = action.payload; // {offset: 10}
return {
...state,
config: {
...state.config,
filters : {
...state.config.filters
}
params : {
...state.config.params,
newParams
}
}
};
....
Curently, I must write this :
return {
...state,
config: {
...state.config,
params : {
...state.config.params,
offset : newParams.offset
}
}
};
How can I have more "generic" in my reducer ? No specify a specific property,but more global.
Just send an object "params" in my action creator, example : this.props.setParams({offset: 30, total: 120}) ?

Actual behavior
Your code is adding newFilters and newParams as properties. That way you would have to access offset as:
state.config.params.newParams.offset
Solution
To access those new filters or params directly with
state.config.params.offset
You should spread newFilters and newParams, using the spread syntax ..., in your reducers:
filters : {
...state.config.filters,
...newFilters
}
and
params : {
...state.config.params,
...newParams
}
The spread syntax will add (or replace) the inner properties of your payload, newFilters and newParams, to the properties of state.config.filters and state.config.params respectively.
Improvements
To simplify your reducer you could spread action.payload directly and remove some probably unnecessary spreads:
actions-search.js
...
case SEARCH.SET_NEW_FILTERS :
return {
...state,
config: {
...state.config,
filters : {
...state.config.filters,
...action.payload
}
}
};
case SEARCH.SET_NEW_PARAMS :
return {
...state,
config: {
...state.config,
params : {
...state.config.params,
...action.payload
}
}
};
...

Related

How do you prevent a javascript object from being converted into a length 1 array of objects?

I'm working on my first solo ReactJS/Redux project and things were going well until I got to a point where I'm using an object in the Redux store that is always supposed to be a single object. When I copy the object from one part of the store (one element of the sources key) to another (the selectedItems key) that object is being stored as an array of length 1, which isn't the data I'm passing in (it's just a single object). I could live with this and just read out of that store variable as an array and just use element 0 except that when I call another method in the reducer to replace that variable in the store, that method stores the new data as a single object! My preference would be to have everything store a single object but I can't figure out how to do that. Anyway, here's some of the reducer code:
const initialState = {
sources: [
{
id: 1,
mfg: 'GE',
system: 'Smart bed',
model: '01',
name: 'GE smart bed'
},
{
id: 2,
mfg: 'IBM',
system: 'Patient monitor',
model: '03',
name: 'IBM patient monitor'
}
],
error: null,
loading: false,
purchased: false,
selectedItem: {}
};
// This is called when a user selects one of sources in the UI
// the Id of the selected sources object is passed in as action.id
// This method creates an array in state.selectedItem
const alertSourceSelect = ( state, action ) => {
let selectedItem = state.sources.filter(function (item) {
return item.id === action.id;
});
if (!selectedItem) selectedItem = {};
return {...state, selectedItem: selectedItem};
};
// When someone edits the selected source, this takes the field name and new value to
// replace in the selected source object and does so. Those values are stored in
// action.field and action.value . However, when the selected source object is updated
// it is updated as a single object and not as an array.
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: updateObject(state.selectedItem[0], { [action.field] : action.value })
};
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ALERT_SOURCE_SELECT: return alertSourceSelect( state, action );
case actionTypes.ALERT_SELECTED_SOURCE_EDIT: return selectedSourceEdit( state, action );
default: return state;
}
Here is the updateObject method (sorry I left it out):
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Issue : updateObject is returning object and not array,and you are maintaining selectedItem as an array not object
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
};
Solution :
Either return array from updateObject :
export const updateObject = (oldObject, updatedProperties) => {
return [{
...oldObject,
...updatedProperties
}]
};
OR make array of returned object
const selectedSourceEdit = ( state, action ) => {
return {
...state,
selectedItem: [updateObject(state.selectedItem[0], { [action.field] : action.value })]
};
};

how to push into nested array in reducer

Above image shows what happens when I trying to dispatch a reply action.
Currently trying to push the input field value etc into the replyComments...but it is still empty even when dispatching this action.... the reducer is checking the majorIndex first and then the minorIndex, from there it should push it into the replyComments. Any one knows how I can fix this?
Link to working Codesandbox
case types.REPLY_COMMENT: {
return {
...state,
enableToggleComment: true,
imageData: state.imageData.map(image => image.id === action.majorIndex ?
{
...image,
comments: {
[action.minorIndex]: {
replyComments: [...image.comments.replyComments, { comment: action.newComment, likeComment: image.toggle, enableReply: false }]
}
}
} : image
)
}
}
EDIT:
I know noticed it's due to a missing key to [action.minorIndex] and when using the ? operator and want to return an object you should add the parenthesis, otherwise you are declaring a scope (like i did whoops)
case types.REPLY_COMMENT: {
return {
...state,
enableToggleComment: true,
imageData: state.imageData.map(image => image.id === action.majorIndex ?
({
...image,
comments: {
[action.minorIndex]: {
replyComments: [...image.comments[action.minorIndex].replyComments, { comment: action.newComment, likeComment: image.toggle, enableReply: false }],
}
}
}) : image
)
}
}
I've only added action.replyText but you should be able to add the rest now
case types.REPLY_COMMENT: {
return {
...state,
enableToggleComment: true,
imageData: state.imageData.map(image =>
image.id === action.generalIndex
? {
...image,
comments: image.comments.map((comment, i) =>
i === action.majorIndex
? {
...comment,
replyComments: [
...comment.replyComments,
action.replyText
]
}
: comment
)
}
: image
)
};
}

how to Modify a child object from you state in the reducer with Redux

I have a state with multiple objects inside it. Right now I can modify all the elements from the first level, like the step property. But I want to know how to modify the elements from an object inside of my state.
Initial state
const initialState = {
step : 1,
user: {
identification : {
email: '',
username: '',
},
localization: {
address: '',
country: '',
}}
My payload is this object:
identification : {
email: "some#html.com"
username: "s032190" }
And my rootReducer is this:
function rootReducer(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return { ...state, user: action.payload }
case CHANGE_STEP:
return { ...state, step: action.payload }
case ADD_FIELD:
Object.keys(state.altaUsuario).forEach (cat => {
return { ...state.user, [cat] : action.payload}
})
default:
return state
}
};
Is it fine to use a loop? I tried with the map or the foreach, nothing works.
I also tried to call property in the spread operator, something like this:
return { ...state, altaUsuario[cat] : action.payload}
But it gives me a syntax error.
I can also modify the payload if is necessary.
Any idea??
You can try something like this
return { ...state,user : {...state.user ,identification :action.payload }}
if your action.payload is
{
identification : {
email: "some#html.com"
username: "s032190"
}
}
you have to override the identification property
return { ...state, user: { ...state.user, identification: action.payload.identification}

Query inside reducer ? redux

How do i write this inside of an reducer to change the state?
doc = {
id:"zf123ada123ad",
name:"examp",
subdoc:{
name:"subdoc examp",
subsubdoc:[{
id:"zcgsdf123zaar21",
subsubsubdoc:[{
id:"af2317bh123",
value: "heyhey" //this value I want to update
}]
}]
}
}
let's say i have an reducer that looks like this
The action.payload look something like this
{
theInputId1: "someId",
theInputId2: "anotherId",
theInputValue: "someValue"
}
export function updateSubSubSubDoc(state = {}, action){
switch(action.type){
case 'UPDATE_THE_SUBSUBSUB':
return {
state.doc.subdoc.subsubdoc.find(x => x.id ==
theInputId1).subsubsubdoc.find(x => x.id == theInputId2).value = theInputValue // just example code for you to understand where i'm going.
}
default:
return state
}
}
What I want to do it update one subsubsub doc in a state that is current
With ES6, this is one way that you could do that:
const initialState = { doc: { subdoc: { subsubdoc: {} } } };
export function doc(state = initialState, action) {
switch (action.type) {
case 'UPDATE_THE_SUBSUBSUB':
const subsubdocIdx = state.doc.subdoc.
subsubdoc.find(s => s.id == action.theInputId1);
const subsubdoc = state.doc.subdoc.subsubdoc[subsubdocIdx];
const subsubsubdocIdx = state.doc.subdoc.
subsubdoc[subsubdocIdx].
subsubsubdoc.find(s => s.id == action.theInputId2);
const subsubsubdoc = state.doc.subdoc.
subsubdoc[subsubdocIdx].
subsubsubdoc[subsubsubdocIdx];
return {
...state,
doc: {
...state.doc,
subdoc: {
...state.doc.subdoc,
subsubdoc: [
...state.doc.subdoc.subsubdoc.slice(0, subsubdocIdx),
{
...subsubdoc,
subsubsubdoc: [
...subsubdoc.slice(0, subsubsubdocIdx),
{
...subsubsubdoc,
value: action.theInputValue,
},
...subsubdoc.subsubsubdoc.slice(subsubsubdocIdx + 1, subsubdoc.subsubsubdoc.length - 1),
],
},
...state.doc.subdoc.subsubdoc.slice(subsubdocIdx + 1, state.doc.subdoc.subsubdoc.length - 1),
]
}
}
}
default:
return state;
}
}
(I haven’t tested this code.)
This is nested the same level as in your example, but you might consider using something like combineReducers to make this a little easier to manage. This is also presupposing you have other actions that create the document chain along the way, and you know these documents exist.
Here's an example how you might be able to do it with combineReducers:
function doc(state = {}, action) {
}
function subdoc(state = {}, action) {
}
function subsubdoc(state = [], action) {
}
function subsubsubdoc(state = [], action) {
switch (action.type) {
case 'UPDATE_THE_SUBSUBSUB':
const idx = state.find(s => s.id == action.theInputId2);
return [
...state.slice(0, idx),
{
...state[idx],
value: action.theInputValue,
},
...state.slice(idx + 1),
];
default:
return state;
}
}
export default combineReducers({
doc,
subdoc,
subsubdoc,
subsubsubdoc,
});
In this example, you don't need action.theInputId1, but you would need to store some reference in the data from the subsubdoc to the subsubsubdoc so that when you're rendering, you can piece it back together. Same with all of the other layers.

Changing a nested array value based on a given index in a reducer while not mutating state

I have a reducer working with an object with arrays. I want to change a value on the nested arrays based on a given index. This code works but I can't seem to get my test to work using deep freeze. I was trying to look at the redux example here http://redux.js.org/docs/basics/Reducers.html using .map to find the index with no luck. Any ideas?
export default function myReducer(state = { toDisplay: [] }, action) {
const { type, groupIndex, itemIndex } = action;
const newObject = Object.assign({}, state);
switch (type) {
case actionTypes.TOGGLE_GROUP:
newObject.toDisplay[groupIndex].isSelected = newObject.toDisplay[groupIndex].isSelected ? false : 'selected';
return newObject;
case actionTypes.TOGGLE_ITEM:
newObject.toDisplay[groupIndex].values[itemIndex].isSelected = newObject.toDisplay[groupIndex].values[itemIndex].isSelected ? false : true;
return newObject;
default:
return state;
}
}
EDIT:
For anyone curious after watching a helpful redux video I came up with this:
export default function myReducer(state = { toDisplay: [] }, action) {
const { type, groupIndex, itemIndex } = action;
switch (type) {
case actionTypes.TOGGLE_GROUP:
return {
...state,
toDisplay: [
...state.toDisplay.slice(0, groupIndex),
{
...state.toDisplay[groupIndex],
isSelected: state.toDisplay[groupIndex].isSelected ? false : 'selected'
},
...state.toDisplay.slice(groupIndex + 1)
]
};
case actionTypes.TOGGLE_ITEM:
return {
...state,
toDisplay: [
...state.toDisplay.slice(0, groupIndex),
{
...state.toDisplay[groupIndex],
values: [
...state.toDisplay[groupIndex].values.slice(0, itemIndex),
{
...state.toDisplay[groupIndex].values[itemIndex],
isSelected: state.toDisplay[groupIndex].values[itemIndex].isSelected ? false : true
},
...state.toDisplay[groupIndex].values.slice(itemIndex + 1)
]
},
...state.toDisplay.slice(groupIndex + 1)
]
};
default:
return state;
}
}
Using a helper/library like suggested is probably the best route but my team wishes to not add another dependency.
Firstly, Object.assign(...) only does a shallow copy. See here.
As you have objects nested inside arrays nested inside objects I would highly recommend the immutability helpers from react (as mentioned by Rafael). These allow you to do something like this:
case actionTypes.TOGGLE_GROUP:
return update(state, {
toDisplay: {
[groupIndex]: {
isSelected: {$set: newObject.toDisplay[groupIndex].isSelected ? false : 'selected'}
}
}
});
If you are looking to modify a simple value inside an array with raw js then you can use something like this:
return list
.slice(0,index)
.concat([list[index] + 1])
.concat(list.slice(index + 1));
(source)

Categories

Resources