Keyu Lin
Keyu Lin

Reputation: 3709

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.

Upvotes: 2

Views: 1593

Answers (3)

Kirill A. Khalitov
Kirill A. Khalitov

Reputation: 1265

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.

Upvotes: 0

nextgentech
nextgentech

Reputation: 3718

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.

Upvotes: 2

David L. Walsh
David L. Walsh

Reputation: 24817

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
    }
}

Upvotes: 3

Related Questions