spondbob
spondbob

Reputation: 1633

reuse redux reducers and actions

Lets say I have a section reducer to hold the section's data

const initialState = {
  data: [],
  section: ''
};

const sectionReducer = function(state = initialState, action) {
  switch(action.type) {
    case types.IMPORT_DATA_SUCCESS:
      return Object.assign(
        {},
        state,
        {
          section: action.section,
          data: action.data
        }
      );
  }
  return state;
}

And I need couple sections with the same data structure and actions. Then how can I do it like this

import { combineReducers } from 'redux';
import sectionReducer from './sectionReducer';

const reducers = combineReducers({
  sectionA: sectionReducer,
  sectionB: sectionReducer
});

export default reducers;

The action is as simple as

import sectionAData from '../data/sectionAData';
...
export const importDataSuccess = (section, data) => {
  return {
    type: types.IMPORT_DATA_SUCCESS,
    section: section,
    data
  }
}

export const loadData = (section) => {
  const dataSet = (() => {
    switch(section) {
      case "SECTIONA":
        return sectionAData
        break;
      case "SECTIONB":
        return sectionBData
        break;
    }
  })()
  return (dispatch) => {
    dispatch(importDataSuccess(section, dataSet))
  }
}

The problem with above approach is everytime I call this.props.dispatch(loadData("SECTIONA")) on componentA and this.props.dispatch(loadData("SECTIONB")) on componentB I get sectionBData on both state tree sectionA and sectionB. Even when sectionA is first dispatched, the sectionB already populated with sectionA data even though it has not been dispatched.

What is the proper way to reuse the reducer and action for such case? Or do I have to create action and reducer for each section even though they are the same?

======= UPDATE =====

Reducer

const initialState = {
  data: []
};
const sectionReducer = function(state = initialState, action) {
  switch(action.type) {
    case types.IMPORT_DATA:
      if ( action.section ) {
        return state.data.push({ section: action.section, data: action.data });
      }
    break;
  }
  return state;
}
...
const reducers = combineReducers({
  sections: sectionReducer
});
export default reducers;

Action

export const importData= (section, data) => {
  return {
    type: types.IMPORT_DATA,
    section,
    data
  }
}
export const loadData = (section) => {
  const dataSet = (() => {
    switch(section) {
      case "SECTIONA":
        return sectionAData
        break;
      case "SECTIONB":
        return sectionBData
        break;
    }
  })()
  return (dispatch) => {
    dispatch(importData(section, dataSet))
  }
}

The component

class SectionWrapper extends Component {
  componentDidMount() {
    this.props.dispatch(loadData(this.props.const))
  }
  render() {
    return (
      <div>
        <Section
          title={this.props.title}
          id={this.props.id}
          data={this.props.data} />
      </div>
    )
  }
}
const mapStateToProps = function(store, ownProps) {
  const section = store.sections.find( (item) => { return item.section === ownProps.const; })
  const data = section.data
  return {
    data
  };
}
SectionWrapper.propTypes = {
  title: PropTypes.string.isRequired,
  id: PropTypes.string.isRequired,
  const: PropTypes.string.isRequired
}
export default connect(mapStateToProps)(SectionWrapper);

Store

const createStoreWithMiddleware  = compose(
  applyMiddleware(
    thunkMiddleware,
    createLogger()
  ),
  window.devToolsExtension ? window.devToolsExtension() : f => f
)

const store = createStore(
  reducers,
  createStoreWithMiddleware
);

export default store;

Upvotes: 0

Views: 1006

Answers (1)

Dhruv Kumar Jha
Dhruv Kumar Jha

Reputation: 6571

Right now, when sectionReducer receives an action of type IMPORT_DATA_SUCCESS, It replaces the entire state with the data received... so there could be only one section and data in the state., Which is obviously what you don't want.

What you can do is, In your sectionReducer

const initialState = {
  data: [],
};

const sectionReducer = function(state = initialState, action) {

  switch(action.type) {
    case types.IMPORT_DATA_SUCCESS:
      // action contains { type, section, data }
      // added if statement to make sure data is not empty.
      if ( action.section && action.data ) {
        state.data.push({ section: action.section, data: action.data });
        return Object.assign( {}, state );
      }
      return state;

    case types.UPDATE_IMPORTED_DATA:
      // action contains { section, data }
      let index = state.data.findIndex( item => { return item.section === action.section; });
      state.data[index] = { section: action.section, data: action.data }
      return state;

    default:
     return state;

  }

}

Using this method you can store n number of sections and also update them very easily.

And when you want to access the data of specific section, you can do

let findSectionByName = ( sections, section_name ) => {
  return sections.find( (item) => { return item.section === section_name; })
}
let section = findSectionByName( state.sections, 'SECTIONA' );

You will also have to change

const reducers = combineReducers({
  sectionA: sectionReducer,
  sectionB: sectionReducer
});

to

const reducers = combineReducers({
  sections: sectionReducer,
});

Upvotes: 2

Related Questions