How to make reducers reusable? - javascript

I have 2 apps inside my CRA that use the same UI but do slightly different things. at the moment I am using lots of similar reducers for the 2 apps and am wondering the best way to make them reusable and not duplicate the code?
app1:
export default (state: App1State = initialState, action: ReducerAction) => {
switch (action.type) {
case APP_1.ACTION:
return {
...state,
id: 123,
app1SpecificState: 'app1'
}
default:
return state
}
}
app2:
export default (state: App2State = initialState, action: ReducerAction) => {
switch (action.type) {
case APP_2.ACTION:
return {
...state,
id: 123,
app2SpecificState: 'app1'
}
default:
return state
}
}
so to combine them I'm thinking of doing something like this:
export default (state: App1State = initialState, action: ReducerAction) => {
if (process.env.APP_NAME === 'app2') {
(state as App2State) = App2State
}
switch (action.type) {
case APP_1.ACTION:
return {
...state,
id: 123,
app1SpecificState: 'app1'
}
case APP_2.ACTION:
return {
...state,
id: 123,
app2SpecificState: 'app2'
}
default:
return state
}
}
I'm wondering if a) this makes sense b) this is a good idea c) there is a better way to achieve this?
I also don't want to repeat the if statement across all my "shared" reducers so would be good to abstract this if possible. probably a HOC, if that's even possible here?

The redux documentation has recipes for that, check it out:
Reusing reducer logic
If you need to create new parts of redux store dynamically, you can also add more reducers later:
Add reducer dynamically

In order to update your store dynamically you should dispatch your data with its property name like this:
dispatch({
type: APP.ACTION,
payload: "app1",
specificState: "app1SpecificState"
})
dispatch({
type: APP.ACTION,
payload: "app2",
specificState: "app2SpecificState"
})
and then in your reducer you can use like this:
case APP.ACTION:
return {
...state,
id: 123,
[action.specificState]: action.payload
}
It is the same principle as updating the state dynamically.

Related

Reducer not returning updated state

I can't get my reducer to return updated state.
The action (confirmed with debugger) is an array of objects - something like: [{name: "test"}, {name: "second_test"}]
I think something must be wrong with my spread operator, although I've also tried Object.assign() in the debugger and that seems to return the expected result.
My components is apparently just getting the default state. Here's my reducer code. Any help would be appreciated. Thank you!
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK':
return {...state, all: [...state.all, action.payload]}
case 'FETCH_DECKS':
debugger;
return {...state, all: action.payload}
default: return state
}
}
I had this very issue recently (exactly as you describe), and I resolved the problem by using the immutability-helper package which is listed here on the ReactJS docs.
This approach allows you to delegate the heavy lifting of state immutability and nested state updates. You should find this resolves your issue:
import update from 'immutability-helper';
const initialState = {
current: {},
all: []
}
export default function deckReducer(state = initialState, action) {
switch(action.type) {
case 'CREATE_DECK': {
/* Add payload to nested state.all list */
return update(state, { all : { $push : [action.payload] }});
}
case 'FETCH_DECKS': {
/* Replace state.all with incoming payload */
return update(state, { all : { $set : action.payload }});
}
default: return state
}
}

update property value of array of object using es6

function posts(state = [], action) {
switch(action.type){
case 'INCREMENT_LIKES':
const i = action.index
return [
...state.slice(0,i),
{...state[i], likes: state[i].likes + 1},
...state.slice(i + 1),
]
default:
return state
}
}
export default posts
Above code worked but for me it's not so elegant, it look super confusing and not so flexible. What if my id is in number, I can't do slice(i + 1), anymore. I can use map with object assign to solve it but I'm looking for more alternative to do update property value of array of object using es6
An alternative would be to make use of update from npm package immutability-helper which is every easy to handle and understand even in case of deep nested states and
import update from `immutability-helper`
function posts(state = [], action) {
switch(action.type){
case 'INCREMENT_LIKES':
const i = action.index
return update(state, {
[i]: {
likes: {
$set: state[i].likes + 1
}
}
})
default:
return state
}
}
export default posts

Dynamic combine of reducers

I have a dashboard with widgets and they depend on the current user. So we don't know which all widgets are going to load at compile time. That said , the state will also have dynamic fields for these widgets.I have a reducer for each widget. How do i combine all the reducers of required widgets ?
My state may look like :
{ dashboardId: 12,
widgetList:{
w1: {
widgetTitle:'widget 1'
data:[]
},
w2:{
widgetTitle:'widget 2'
data:[]
}
}
}
Now i want to combine Reducers:
combineReducers({
w1:widget1Reducer,
w2:widget2Reducer
})
Now, the issue is we dont know which all widgets are going to load for the current dashboard.
You don't need to combine multiple reducers using the helper. Create a widgetList reducer that will update to specified widget using the appropriate widget reducer:
function widgetList(state = {}, action) {
switch (action.type) {
case WIDGET_A_FOO:
case WIDGET_A_BAR:
return {
...state,
[action.id]: widgetA(state[action.id], action),
}
case WIDGET_B_BAZ:
return {
...state,
[action.id]: widgetB(state[action.id], action),
}
case DELETE_WIDGET:
return _.omit(state, action.id)
default:
return state
}
}
In the above example, action.id holds the widget ID. widgetA and widgetB are the reducers of widget A and widget B.

dispatch action triggering, but redux store not updating

Currently in my React-Redux app, I would like to use React to set the state of show to either false, or true. When set to true, the app will initialize. (There are multiple components, so it would make sense to do this using react/redux.)
However, my current issue is that even though I have connected my app using react redux, and my store using provider, the dispatch action will be called, without updating the store(I am using redux dev tools to double check, as well as in app testing).
I have attached the code I feel is relevant, however, the entire code base is available as a branch specifically made for this question here. I have spent quite sometime on this(actually an understatement) and any contributions would be greatly appreciated.
Component Relevant Code
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
return(
<div className = "works">
<button id="show-block" type="button" className="show-hide-button" onClick={this.showBlock}>show</button>
<button id="hide-block" type="button" className="show-hide-button" onClick={this.hideBlock}>Hide</button>
</div>
);
function mapStateToProps(state) {
const {environment} = state;
return{
environment
}
}
export default connect(mapStateToProps)(Form);
Action
import * as types from "../constants/ActionTypes";
export function showBlock(show) {
return {
type: types.SHOW,
show: true
};
}
export function hideBlock(hide) {
return {
type: types.HIDE,
show: false
};
}
Reducer
import * as types from "../constants/ActionTypes";
const initialState = {
show: false
};
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
type: types.HIDE
});
case types.SHOW:
return Object.assign({}, state, {
type: types.SHOW
});
default:
return state;
}
}
Thank you, and once again any help is very much so appreciated.
So, I asked a co-worker for help and it turns out that I was returning my action as an object instead of a function. So, for instance, changing the following code:
hideBlock(){
const{dispatch} = this.props;
dispatch(hideBlock);
}
to
hideBlock(){
const{dispatch} = this.props;
//change hideBlock to hideBlock()
dispatch(hideBlock());
}
solved the issue. Thanks Andrew!
It looks like state.show is set in initialState but never modified in any of the cases inside the reducer. The action has show: true, but the reducer never uses this to update the state.
Here's a simplified reducer that updates state.show based on the action's show field:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
case types.SHOW:
return Object.assign({}, state, {
show: action.show
});
default:
return state;
}
}
Alternatively, because action.show and action.type have the same data, you could remove show from the actions and rely on the action type:
export default function environment(state = initialState, action) {
switch(action.type) {
case types.HIDE:
return Object.assign({}, state, {
show: false
});
case types.SHOW:
return Object.assign({}, state, {
show: true
});
default:
return state;
}
}

Dynamic keys using Immutable.js

I'm using Redux, but NOT React to provide a bit of context. I have a situation where I would like to store some data about pages a user has visited, but it seems like I need to set my schema prior to setting any new state in a reducer.
function reducer(state, action) {
switch(action.type) {
case STORE_CATEGORY:
return Object.assign(
{}, state,{
[action.category] : {
pages: [],
filters: {}
}
}
);
}
}
The above function works, but I want to translate updating the state with an undetermined set of keys using immutable.js where the keys will be named after the category visited. Thanks.
As I understand, the Map from Immutable would be good for you issue
const initalState = Immutable.Map();
function reducer(state = initalState, action) {
switch(action.type) {
case STORE_CATEGORY:
return state.set(action.category, {
pages: [],
filters: {}
});
}
}

Categories

Resources