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
}
}
Related
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;
}
I'm looking at this documentation page: https://redux.js.org/recipes/structuring-reducers/reusing-reducer-logic on redux. I'm trying to implement something like this:
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
function createNamedWrapperReducer(reducerFunction, reducerName) {
return (state, action) => {
const { name } = action
const isInitializationCall = state === undefined
if (name !== reducerName && !isInitializationCall) return state
return reducerFunction(state, action)
}
}
const rootReducer = combineReducers({
counterA: createNamedWrapperReducer(counter, 'A'),
counterB: createNamedWrapperReducer(counter, 'B'),
counterC: createNamedWrapperReducer(counter, 'C')
})
However I don't understand how to get the name from action. And this code is not working on my project as const { name } = action is returning undefined.
What I'm I missing? Is this example complete? How can I fix this?
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]
}
I have below actions and reducer:
actions:
import { OPEN_NODE, CLOSE_NODE, GET_NODES } from '../constants/NodeActionTypes';
export function openNode(path) {
return {
type: OPEN_NODE,
path: path
};
}
export function closeNode() {
return {
type: CLOSE_NODE
};
}
export function getNodes(path) {
return {
type: GET_NODES,
path: path
};
}
reducer:
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
open:true,
nodes:nodes
};
case CLOSE_NODE:
return {
...state,
open:false
};
case GET_NODES:
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
nodes:nodes
};
default:
return state;
}
}
Obviously, OPEN_NODE contain GET_NODES (only plus open:true), but there seems many way to organize the code:
pack GET_NODES reducer to a function, call this in OPEN_NODE , and add open:true.
modify openNode action, send [OPEN_NODE, GET_NODES] together , but how to write switch(action.type)'s case ?
let OPEN_NODE reducer dispatch a getNodes action to trigger GET_NODES reducer
which is best ? Or any another better way?
You don't have to keep everything inside your switch statement. If you have 2 similar actions, just refactor into a private function and call it.
In your case, it might be something like:
// your reducer helper
const getNodes = (state) => {
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
nodes:nodes
};
};
// your reducer function
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
return { ...getNodes(state), open:true };
case GET_NODES:
return getNodes(state);
// ....
}
You can simply use the switch statement to execute both actions :
export default function opener(state = initialState, action) {
switch (action.type) {
case OPEN_NODE:
case GET_NODES:
var { path } = action
var {nodes} = getFileList(path)
return {
...state,
nodes:nodes
open: action.type === OPEN_NODE ? true : state.open
};
case CLOSE_NODE:
return {
...state,
open:false
};
default:
return state;
}
}
Check out my github project for creating generic reducers. The solution I purpose will address many of the concerns you currently have.
Redux-Reducer-Generator
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;