Adding specialized functionality to instance of a reducer factory - redux

In order to decrease code duplication in the redux-related part of my app, I have come up with reducer factories in order to group repetative logic into an abstract reducer which is then turned into concrete reducer instances by passing name param to the factory.
ReduxFactory/reducer.js
const initialState = {
foo: null
}
function reducerFactory(name = '') {
return function reducer(state = initialState, action) {
switch (action.type) {
case `${name}_DO_SOMETHING`: {
return state
}
default:
return state;
}
}
}
export default reducerFactory;
Then I have two modules for which I build instances of a reducer factory:
fileA.js
import reducerFactory from './ReduxFactory/reducer'
const reducer = reducerFactory('OBJECT_A')
fileB.js
import reducerFactory from './ReduxFactory/reducer'
const reducer = reducerFactory('OBJECT_B')
Now imagine that on objects of type B I need to implement some ad-hoc functionality, which I do not want to include into the general reducer factory body because it is too specialized. Is there any valid JS code pattern to implement this ?

If I understand you correctly I think you can do the following:
function reducerFactory(name = '', extras = (x) => x) {
return function reducer(state = initialState, action) {
switch (action.type) {
case `${name}_DO_SOMETHING`: {
return state;
}
default:
return extras(state, action);
}
};
}
const reducer = reducerFactory(
'OBJECT_B',
(state, action) => {
if (action.type === 'extra') {
//return changed state
}
return state;
}
);

Related

How do i write data from localStorage in the reducer?

How do i write data from localStorage in the reducer? Now it looks like this, but I doubt it is right.
const initialState = {
entities: JSON.parse(localStorage.getItem(TODOS)!) || []
}
export default (state = initialState, action: any) => {
const {type, payload} = action
switch (type) {
case ADD_TODO:
case REMOVE_TODO:
case REMOVE_ALL_TODOS:
default:
return state
}
}

Redux dispatch action

I am just trying to run a simple redux program when i use command node index it shows me error that action must be plain objects below is my code for that
const redux = require('redux')
const createStore = redux.createStore
const BUY_CAKE = 'BUY_CAKE'
function buyCake () {
return
{
type: BUY_CAKE
}
}
const initialState = {
numOfCakes: 10
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case BUY_CAKE: return {
...state,
numOfCakes: state.numOfCakes - 1
}
default: return state
}
}
const store = createStore(reducer)
console.log("initial state is ", store.getState())
const unsubscribe = store.subscribe(() => console.log("updated", store.getState()))
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
store.dispatch(buyCake())
unsubscribe()
when i dispatch(buyCake()) then only it shows error but if i do store.dispatch({type:BUY_CAKE}) then code runs fine why is the error occuring
Because your return statement is wrongly formatted. Be aware of this deadly feature:
JavaScript will automatically insert semicolons. Without the parentheses, JavaScript would ignore the following lines and return without a value.
This is your function with semicolons, which will return undefined:
function buyCake(){
return;
{
type:BUY_CAKE
};
};
Solution: Move your curly brackets to the return line:
function buyCake(){
return {
type: BUY_CAKE
}
}

Redux: What is the best way to toggle a boolean value in a normalized state tree?

I'm currently developing an app with React Native. The state of the app is quite complex, but managable due to Redux and Normalizr. I now have to implement a functionality for the user to filter items.
In order for the user to filter items, I enriched the server response in the Normalizr schema:
export const subCategorySchema = new schema.Entity(
"subCategories",
{},
{
idAttribute: "uuid",
processStrategy: entity => {
const newEntity = Object.assign({}, { name: entity.name, uuid: entity.uuid, chosen: false });
return newEntity;
}
}
);
The corresponding reducer now looks like this:
const initialState = {};
const subCategoriesReducer = (state = initialState, action) => {
if (action.payload && action.payload.entities) {
return {
...state,
...action.payload.entities.subCategories
};
} else {
return state;
}
};
These the subcategories now get displayed in the UI using this SwitchListItem component, which gets it's items through a selector:
import React, { Component } from "react";
import { Switch, Text, View } from "react-native";
import PropTypes from "prop-types";
import styles, { onColor } from "./styles";
export default class SwitchListItem extends Component {
static propTypes = {
item: PropTypes.object
};
render() {
const { name, chosen } = this.props.item;
return (
<View style={styles.container}>
<Text style={styles.switchListText}>{name}</Text>
<Switch style={styles.switch} value={chosen} onTintColor={onColor} />
</View>
);
}
}
I'm now about to implement the <Switch /> component's onValueChange() function, which is where my question arose:
What is the best way to toggle a boolean value in a normalized state tree?
I came up with two solutions, which I will describe below. Please let me know if you think any one of these is good. If not I would love to get advice on what I could do better :)
Solution 1: Extending the reducer:
My first solution for the problem was to extend the reducer to listen to TOGGLE_ITEM actions. This would look something like this:
const subCategoriesReducer = (state = initialState, action) => {
switch (action.type) {
case TOGGLE_ITEM:
if (action.payload.item.uuid in state) return { ...state, ...action.payload.item };
}
if (action.payload && action.payload.entities) {
return {
...state,
...action.payload.entities.subCategories
};
} else {
return state;
}
};
This is my preferred solution as it does not need a lot of code.
Solution 2: Enriching the selector that passes the items to the SwitchList:
The other solution would be to enrich the objects while being passed to the list using a selector with it's key for the state. Then I could create an action that uses this key to update the state like this:
const toggleItem = (item, stateKey) => ({
type: TOGGLE_ITEM,
payload: {entities: { [stateKey]: item } }
})
I would love to read an answer, preferably opinionated, if you have a lot of experience with Redux. Also, if you think my way of enriching the data in the normalizr is bad and you can come up with a better way, please let me know! Thank you very much for any advice!
I did it in a completely different way.
I created an array that holds the uuids of the toggled items. Therefore I only need to look, whether the item is in the toggled array.
Just like this:
const initialState = {};
export const byId = (state = initialState, action) => {
if (action.payload && action.payload.entities && action.payload.entities.itemClassifications) {
return {
...state,
...action.payload.entities.itemClassifications
};
} else {
return state;
}
};
export const chosen = (state = [], action) => {
if (action.type === TOGGLE_ITEM && action.meta === ITEM_CLASSIFICATION) {
if (state.includes(action.payload.uuid)) {
return state.filter(uuid => uuid !== action.payload.uuid);
} else {
return [...state, action.payload.uuid];
}
} else {
return state;
}
};
const itemClassificationsReducer = combineReducers({
byId,
chosen
});
export default itemClassificationsReducer;
export const getAllItemClassificationsSelector = state =>
Object.values(state.itemClassifications.byId);
export const getAllItemClassificationsNormalizedSelector = state => state.itemClassifications.byId;
export const getChosenItemClassificationsSelector = state => state.itemClassifications.chosen;
export const enrichAllItemClassificationsSelector = createSelector(
getAllItemClassificationsSelector,
itemClassifications =>
itemClassifications.map(val => ({ ...val, stateKey: ITEM_CLASSIFICATION }))
);
export const getItemClassificationsFilterActiveSelector = createSelector(
getChosenItemClassificationsSelector,
itemClassifications => itemClassifications.length > 0
);

Redux Thunk - State Undefined After Dispatching Multiple Actions

When I dispatch an action after dispatching another action, I noticed that my state isFetching becomes undefined. I think it's probably some asynchronous issue with the actions dispatching after the other. How would I fix this so that I can get both actions to dispatch correctly?
My redux module:
const FETCHING = 'FETCHING'
const FETCHING_KEYWORD = 'FETCHING_KEYWORD'
function fetching() {
return {
type: FETCHING,
}
}
function settingKeyWord(keyWord) {
return {
type: FETCHING_KEYWORD,
keyWord,
}
}
export function fetchKeyWord (keyWord) {
return (dispatch, getState) => {
const { isFetching } = getState()
const { keyWord } = getState()
// After I dispatch the two actions here, isFetching becomes undefined
dispatch(fetching())
dispatch(settingKeyWord(keyWord))
}
}
const initialState = {
isFetching: false,
keyWord: '',
}
export default function results(state = initialState, action) {
switch (action.type) {
case FETCHING:
return {
isFetching: true,
}
case FETCHING_KEYWORD:
return {
keyWord: action.keyWord,
}
default:
return state
}
}
The reducer cases need to return the entire state, not just the updated part, so the problem should also occur when dispatching either action normally. You can fix it by using Object.assign or object-spread syntax in the reducer cases. For example, for Fetching:
case FETCHING:
return Object.assign((), state, {
isFetching: true,
})
or
case FETCHING:
return {...state,
isFetching: true,
})

Redux action reuse

I'm a beginner in react / redux.
I've finished a basic component <HeatMap /> in my app, with its actions / reducer / store and it works well.
And I'll render another <HeatMap /> with different settings (props).
What I'm trying to do is to separate this 2 component, because when i dispatch an update action in one, the other one performed it simultaneously.
Question 1
I tried this to separate the states in store
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
heatMap1: heatMap,
heatMap2: heatMap
});
combineReducers and connectthe 2 heatmap in different object in store
export default connect((state)=> {
return {
onState: state.heatMap1.onState,
config: state.heatMap1.config
}
})(CHMSHeatMap1)
and
export default connect((state)=> {
return {
onState: state.heatMap2.onState,
config: state.heatMap2.config
}
})(CHMSHeatMap2)
is this correct?
Question 2
Because 2 component both react when action is dispatched
I'm thinking about separating the shared actions, but I don't think it's a good idea. Or maybe the issue is not here.
So can you tell me what cause this problem and how to solve it?
Here are my reducer
import * as actionTypes from '../actions/heat-map';
import Immutable from 'immutable';
const onState = {
fetching: 'FETCHING',
error: 'ERROR',
drawn: 'DRAWN'
};
const initialState = {
onState: onState.fetching,
config: {}
};
export default function heatMapReducer(state = initialState, action) {
let immutableState = Immutable.fromJS(state);
switch (action.type) {
case actionTypes.INITIALIZING:
return immutableState.set('onState', onState.drawn).set('config', action.payload.initConfig).toJS();
case actionTypes.FETCH_DATA_REQUEST:
return immutableState.set('onState', onState.fetching).toJS();
case actionTypes.FETCH_DATA_SUCCESS:
return immutableState.set('onState', onState.drawn).setIn(['config','series',0,'data'],Immutable.fromJS(action.payload.mapData.data)).toJS();
case actionTypes.FETCH_DATA_FAILURE:
return immutableState.set('onState', onState.error).set('config', action.payload.mapData).toJS();
default:
return state;
}
}
Action is simple
export function initializeConfig(initConfig) {
return {
type: INITIALIZING,
payload: {
text: 'Initializing',
initConfig
}
}
}
export function requireMapData() {
return {
type: FETCH_DATA_REQUEST,
payload: {
text: 'Loading'
}
};
}
..........
//Async Action for fetching map data and redraw the map
export function fetchMapData(address) {
return function (dispatch) {
//dispatch requireMapData action to set the map in loading state
dispatch(requireMapData());
return fetch(address)
.then(fetchUtil.checkHttpStatus) //check if 404
.then(fetchUtil.parseJSON)
.then(mapData => dispatch(fetchDataSucceed(mapData)))
.catch(error => dispatch(fetchDataFailed(error)));
}
}
Thank you my friend.
You cannot duplicate your reducers in the manner you've depicted. Both are going to respond in the exact same way to the exact same actions.
The solution is to have all of your heat map data in the same reducer state. e.g.
const initialState = {
heatMap1: {},
heatMap2: {}
};
export default heatmap(state = initialState, action) {
// etc
Now if you want to use the same actions for both heat maps, you'll need to have an action property specifying which heap map you're targeting. If you have several heat maps, I'd recommend an array of heat maps with each action containing an index or id to target a particular heat map. e.g.
function updateHeatMap(index, value) {
return {
type: UPDATE_HEATMAP,
index: index,
value: value
}
}
You can also take a look at the multireducer module (https://github.com/erikras/multireducer). It was designed to solve exactly the scenario you propose.
So you would be able to configure your store as such:
import multireducer from 'multireducer';
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
multireducer: multireducer({
heatMap1: heatMap,
heatMap2: heatMap
})
});
After that, you would then need to use connectMultireducer() instead of redux's standard connect() in order to connect the specific slice of the store to particular components like so:
export default connectMultireducer((state)=> {
return {
onState: state.heatMap.onState,
config: state.heatMap.config
}
})(CHMSHeatMap)
And finally in order to get the correct part of the state to each of those components you would pass in the key when rendering them as such:
<CHMSHeatMap multireducerKey="heatMap1"/>
<CHMSHeatMap multireducerKey="heatMap2"/>
Obviously it's better to read the actual docs at the multireducer repo, but that should give a brief overview. Basically the module is just abstracting the process of adding a key-based lookup to each reducer that is created through the multireducer function.
I suggest original concept of multireducer working without any libraries.
The base idea is unique Symbol action types and self-contained Redux-module like this:
import * as services from './../../../api/services';
const initialState = {
list: [],
};
function getListReducer(state, action) {
return {
...state,
list: action.payload.list,
};
}
function removeItemReducer(state, action) {
const { payload } = action;
const list = state.list.filter((item, i) => i !== payload.index);
return {
...state,
list,
};
}
export default class List {
constructor() {
// action types constants
this.GET_LIST = Symbol('GET_LIST');
this.REMOVE_ITEM = Symbol('REMOVE_ITEM');
}
getList = (serviceName) => {
return async (dispatch) => {
const list = await services[serviceName].get();
dispatch({
type: this.GET_LIST,
payload: {
list,
serviceName,
},
});
};
}
removeItem = (index) => {
return (dispatch) => {
dispatch({
type: this.REMOVE_ITEM,
payload: {
index,
},
});
};
}
reducer = (state = initialState, action) => {
switch (action.type) {
case this.GET_LIST:
return getListReducer(state, action);
case this.REMOVE_ITEM:
return removeItemReducer(state, action);
default:
return state;
}
}
}
More information read there.

Resources