How can I update 1 property of object in reducer? - javascript

I have a reduxreducer which looks like this:
const initialState = {
visitedData:{specialty:false,review:false,reviewUpgrade:false}
}
const myred= (state = initialState, action) => {
switch (action.type) {
case 'GO_FOR_IT_SON':
return Object.assign({}, state, { visitedData: action.data });
default:
return state
}
}
Now from my reactcomponent I will call something like:
store.dispatch({type: 'GO_FOR_IT_SON', data:{review:true} });
or:
store.dispatch({type: 'GO_FOR_IT_SON', data:{specialty:false} });
So each of these statements are supposed to set one of the properties of visitedData to true/false and leaving the other ones intact.
How can I set each of the properties of visitedData to true/false and leaving the other properties unchanged?

const initialState = {
visitedData: {
specialty: false,
review: false,
reviewUpgrade: false
}
}
const myred = (state = initialState, action) => {
switch (action.type) {
case 'GO_FOR_IT_SON':
return {...state, visitedData: {...state.visitedData, action.data}}
default:
return state
}
}
as you did with state - Object.assign, you should do the same with visitedData object or use spread operator instead object.assign

I would recommend to have a reducer and action for every changable property:
import { combineReducers } from 'redux';
const specialty = (state = false, action) => {
if (action.type === 'TOGGLE_SPECIALTY') {
return action.data;
}
return state;
};
const review = (state = false, action) => {
if (action.type === 'TOGGLE_REVIEW') {
return action.data;
}
return state;
};
const myred = combineReducers({
specialty,
review
});
But in your situation, the solution would be:
const myred= (state = initialState, action) => {
switch (action.type) {
case 'GO_FOR_IT_SON':
return Object.assign({}, state, {
visitedData: Object.assign({}, state.visitedData, action.data)
});
default:
return state
}
}

I think this will work:
return Object.assign({}, state, {
visitedData: Object.assign({}, state.visitedData, action.data)
});
Check this example:
let a = {b: {a : 1, b : 2, c : 5} };
let b = {a : 5};
let k = Object.assign({}, a, {b: Object.assign({}, a.b, b)});
console.log(k);

Same as other solution. I suggest to separate object merging into another line and use object spread for more readable
const myred = (state = initialState, action) => {
switch (action.type) {
case 'GO_FOR_IT_SON':
let newVisitedData = {
...state.visitedData,
...action.data
}
return { visitedData: newVisitedData }
default:
return state
}
}

A straight forward brute force ways:
Edit use lodash cloneDeep
const _ = require('lodash')
let newState = _.cloneDeep(state)
for(let key in action) {
newState.visitedData[key] = action[key]
}

Related

How to update an array using redux reducer, I want to change a boolean property inside an array of objects to make i true or false

I have an array, every time I fire an action it adds a new item in the array with the value true and or false, I need to change that code to return the real number of the array not adding new items
Here is my code,
import {ads} from '../../data/ads';
import {ADD_TO_FAVORITE} from '../types';
interface ActionInter {
type: string;
payload: {id: number};
}
const initialState = {
allAds: ads,
myFavorite: [],
};
const myFavorite = (state = initialState, action: ActionInter) => {
switch (action.type) {
case ADD_TO_FAVORITE:
const itemFav = state.allAds[action.payload.id - 1].isFav;
console.log(itemFav);
if (itemFav === true)
return {
...state,
allAds: [
...state.allAds,
(state.allAds[action.payload.id - 1].isFav = false),
],
};
if (itemFav === false)
return {
...state,
allAds: [
...state.allAds,
(state.allAds[action.payload.id - 1].isFav = true),
],
};
}
return state;
};
export default myFavorite;
ads, it's an array of objects
You can using map like this:
const myFavorite = (state = initialState, action: ActionInter) => {
switch (action.type) {
case ADD_TO_FAVORITE:
return {
...state,
allAds: state.allAds.map((item, index) => {
return action.payload.id - 1 === index
? {
...item,
isFav: !item.isFav,
}
: item;
}),
};
}
return state;
};
You can try this:
const myFavorite = (state = initialState, action: ActionInter) => {
switch (action.type) {
case ADD_TO_FAVORITE:
const index = action.payload.id - 1;
const newAllAds = [...state.allAds];
newAllAds[index].isFav = !newAllAds[index].isFav;
return {
...state,
allAds: newAllAds,
};
}
return state;
};
You have to use immutability, instead of updating the previous state, create a copy and update that copy
const myFavorite = (state = initialState, action: ActionInter) => {
switch (action.type) {
case ADD_TO_FAVORITE: {
// Using the spread operator we'll create a copy
const allAdsCopy = [...state.allAds];
// Identify the item index
const index = action.payload.id -1;
// Update the copy
allAdsCopy[index].isFav = !allAdsCopy[index].isFav;
return {
...state,
allAds : allAdsCopy,
};
}
default:
return state;
}
};

TypeError: Cannot read property 'concat' of undefined in ReactJs

I get:
TypeError: Cannot read property 'concat' of undefined
however, I have define the 'orders' array in my initialState.
Does somebody know the reason?
import * as actionTypes from '../actions/actionTypes.js';
import { updateObject } from '../utility.js';
const initialState = {
orders: [],
loading: false,
purchased: false
};
const purchaseInit = (state, action) => {
return updateObject(state, { purchased: false });
};
const purchaseBurgerStart = (state, action) => {
return updateObject(state, { loading: true });
};
const purchaseBurgerSuccess = (state, action) => {
const newOrder = updateObject(action.orderData, { id: action.orderId });
return updateObject(state, {
loading: false,
purchased: true,
orders: state.orders.concat(newOrder)
});
};
const purchaseBurgerFail = (state, action) => {
return updateObject(state, { loading: false });
};
const fetchOrdersStart = (state, action) => {
return updateObject(state, { loading: true });
};
const fetchOrdersSuccess = (state, action) => {
return updateObject(state, {
orders: action.orders,
loading: false
});
};
const fetchOrdersFail = (state, action) => {
return updateObject(state, { loading: false });
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.PURCHASE_INIT: return purchaseInit(state, action);
case actionTypes.PURCHASE_BURGER_START: return purchaseBurgerStart(state, action);
case actionTypes.PURCHASE_BURGER_SUCCESS: return purchaseBurgerSuccess(state, action);
case actionTypes.PURCHASE_BURGER_FAIL: return purchaseBurgerFail(state, action);
case actionTypes.FETCH_ORDERS_START: return fetchOrdersStart(state, action);
case actionTypes.FETCH_ORDERS_SUCCESS: return fetchOrdersSuccess(state, action);
case actionTypes.FETCH_ORDERS_FAIL: return fetchOrdersFail(state, action);
default: return { state };
}
};
export default reducer;
Please check the state parameter this parameter does not have a state array, you can use a console.log to check what this parameter has.
I think you are missing to assign the state as initial state in the parameter.
const purchaseBurgerSuccess = (state = initialState, action)
1) This line...
case actionTypes.PURCHASE_BURGER_SUCCESS: return purchaseBurgerSuccess(state, action);
...as with all the other lines in that switch statement should be returning a new state.
So in purchaseBurgerSucess you need to make sure you're returning a new state using the state you're passing in as an argument:
const purchaseBurgerSuccess = (state, action) => {
const newOrder = updateObject(action.orderData, { id: action.orderId });
// Spread out the state you pass in as an argument
// and update those properties that have changed
return {
...state,
loading: false,
purchased: true,
orders: state.orders.concat(newOrder)
};
};
Note: your other functions fall into this trap too so those will also need to be updated.
2) Your default case in your switch statement should be:
default: return state;

Is it possible to merge two reducers into one?

I would like to merge two reducers, the first being created as a generic one and the second one would be more specific to it's own state. Both these reducers would not handle the same cases. Merging these would only result in default case being duplicated, the default case always returning the default state anyways. This would help as I would only test the generic one once.
In case you were thinking about reduceReducers or combineReducers, that would not work since I have many "special" reducers with every one of them having the same action type to handle and all of those reducers have a different part of the state to modify.
const initialState = {
byId : {},
ids: []
}
const dogsReducer = ({ dogs: state = initialState, ...restOfState }, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state.dogs, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
case SPECIFIC_DOG_ACTION:
...
default:
return state
}
}
const catsReducer = ({ cats: state = initialState, ...restOfState}, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
case SPECIFIC_CAT_ACTION:
...
default:
return state
}
}
I want to isolate the following cases : INITIALIZE and RESET in a generic switch/case function or a generic reducer, so I would only have to test those cases once and not in every reducer. There would be more generic cases in the future, that's why I want to avoid repetition.
This is the expected result :
const genericReducer = (state = initialState, action) => {
switch (action.type) {
case INITIALIZE:
return {
byId : _.keyBy(state.dogs, 'id'),
ids: state.map(({id}) => id)
}
case RESET:
return initialState
default:
return state
}
}
const dogsReducer = ({ dogs: state = initialState, ...restOfState }, action) => {
switch (action.type) {
case SPECIFIC_DOG_ACTION:
...
default:
return state
}
}
const catsReducer = ({ cats: state = initialState, ...restOfState}, action) => {
switch (action.type) {
case SPECIFIC_CAT_ACTION:
...
default:
return state
}
}
const finalCatsReducer = mergeReducers(catsReducer, genericReducer)
const finalDogsReducer = mergeReducers(dogsReducer, genericReducer)
I could imagine using the following however I want to say that would have a similar effect to just putting them all flat. The only added benefit I can see is that the specific switch cases won't be verified unless the general cases fail.
const genericSwitch = type => {
switch (type) {
case 1:
do something x
default: //specificSwitch
switch (type) {
case 2:
do something y
default:
return z
}
}
}
The simplest solution is to wrap into the upper method:
const combinedSwitch = type => {
const result = genericSwitch(type);
return result === z ? specificSwitch(type) : result;
}
const genericSwitch = type => {
switch (type) {
case 1:
do something x
default:
return z
}
}
const specificSwitch = type => {
switch (type) {
case 2:
do something y
default:
return z
}
}

Is this redux reducer OK

Is this reducer OK:
function someReducer(state = initialState, action) {
if (action.type === SOME_ACTION) {
const newState = Object.assign( {}, state );
// ...
// doing whatever I want with newState
// ...
return newState;
}
return state;
}
and if is OK, why we need all those immutable libraries to complicate our lives.
p.s
Just trying to comprehend Redux and immutability
export default function (state = initialState, action) {
const actions = {
SOME_ACTION: () => {
return {
...state
}
},
ANOTHER_ACTION: () => {
return {
...state
error: action.error
}
},
DEFAULT: () => state;
}
return actions[action.type] ? actions[action.type]() : actions.DEFAULT();
}
I prefer doing this instead. I am not a big fan of switch statements.
The standard approach is to use a switch/case with spread syntax (...) in your reducer.
export default function (state = initialState, action) {
switch (action.type) {
case constants.SOME_ACTION:
return {
...state,
newProperty: action.newProperty
};
case constants.ERROR_ACTION:
return {
...state,
error: action.error
};
case constants.MORE_DEEP_ACTION:
return {
...state,
users: {
...state.users,
user1: action.users.user1
}
};
default:
return {
...state
}
}
}
You can then use ES6 spread syntax to return your old state with whatever new properties you want changed/added to it.
You can read more about this approach here...
https://redux.js.org/recipes/using-object-spread-operator
I found something that I really like:
import createReducer from 'redux-starter-kit';
const someReducer = createReducer( initialState, {
SOME_ACTION: (state) => { /* doing whatever I want with this local State */ },
SOME_ANOTHER_ACTION: (state) => { /* doing whatever I want with local State */ },
THIRD_ACTION: (state, action) => { ... },
});
If your state has nested objects or arrays, Object.assign or ... will copy references to your older state variable and it may cause some issue. This is the reason why some developers use immutable libraries as in most of the case state has deep nested array or objects.
function someReducer(state = initialState, action) {
if (action.type === SOME_ACTION) {
const newState = Object.assign( {}, state );
// newState can still have references to your older state values if they are array or orobjects
return newState;
}
return state;
}

redux - how to store and update a key/value pair

I am using redux wth reactjs.
I want to store simple key/value pairs but can't get the reducer syntax right.
In this case each key/value pair will hold a connection to an external system.
Is this the right way to do it? I'm at the beginning with redux so it's a bit of mystery.
export default (state = {}, action) => {
switch(action.type) {
case 'addConnection':
return {
connections: {
...state.connections, {
action.compositeKey: action.connection
}
}
default:
return state
}
}
This worked for me:
export default (state = {}, action) => {
switch(action.type) {
case 'addConnection':
return {
...state,
connections: {
...state.connections,
[action.compositeKey]: action.connection
}
}
default:
return state
}
}
From the docs:
https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns#correct-approach-copying-all-levels-of-nested-data
You just have a couple mistakes with {} instead of [] and forgetting to use Object.assign.
const reducer = (state = {}, action) => {
switch (action.type) {
case 'addConnection':
return Object.assign({}, state, {
connections: [
...state.connections,
{
[actions.compositeKey]: action.connection
}
]
});
default:
return state;
}
}
export default reducer;
It might help to see it expressed this way too. It does the same thing but I think it reads a little nicer
const reducer = (state = {}, {type, compositeKey, connection}) => {
switch (type) {
case 'addConnection':
return Object.assign({}, state, {
connections: state.connections.concat({
[compositeKey]: connection
})
});
default:
return state;
}
}
export default reducer;
Or if you're using Immutable, something like this
import Immutable from 'immutable';
const reducer = (state = Immutable.Map(), {type, compositeKey, connection}) => {
switch (type) {
case 'addConnection':
return state.set(
'connections',
state.get('connections').concat({
[compositeKey]: connection
})
);
default:
return state;
}
}
export default reducer;
This may work
const reducer = (state = {}, {type, compositeKey, connection}) => {
switch (type) {
case 'addConnection':
var newData={};
newData[compositeKey]=connection;
return Object.assign({}, state, newData)
});
default:
return state;
}
}
export default reducer;

Categories

Resources