Skip to content

Instantly share code, notes, and snippets.

@fourestfire
Last active April 7, 2017 03:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fourestfire/1a9cc3c49f7c8276673ec4fb3592bec2 to your computer and use it in GitHub Desktop.
Save fourestfire/1a9cc3c49f7c8276673ec4fb3592bec2 to your computer and use it in GitHub Desktop.
Boilerplate for react-redux implementation, including react-router

React-Redux Patterns and Boilerplate

Constants, Action Creators (sync), Reducers, and Action Creators (async)

/* -------------------<   ACTIONS   >--------------------- */
const MY_ACTION = 'MY_ACTION';


/* ---------------<   ACTION CREATORS   >------------------- */
// we export here if we're not using thunk, as these are sync action creators

export const actionCreator = (payload) => {
  return {
    type: 'MY_ACTION',
    payload
  };
};


/* -------------------<   REDUCERS   >--------------------- */
// we export here to supply our reducers to the store
// this is where we set initial state. accessible in components through this.props.reducerName.property
let initialState = { property1: [], property2: {} };

export default function (state = initialState, action) {
  switch (action.type) {
    case MY_ACTION:
      return Object.assign({}, state, {property1: action.payload});
    default:
      return state;
  }
}


/* ------------------<   DISPATCHERS   >-------------------- */
// these are async action creators, enabled with thunk
// otherwise, redux should dispatch from within container using the sync action creator

import axios from 'axios';

export const asyncActionCreator = (param) => dispatch => {
  axios.get(`/api/backend/${param}`)
  .then(res => res.data)
  .then(thingWeGetBack => dispatch(actionCreator(thingWeGetBack)))
  .catch(err => console.error('dang, we hit an error..!', err));
}

Components and Containers

/* --------------<   PRESENTATIONAL COMPONENT   >------------- */
// assumes combineReducers; otherwise use props.property instead of props.reducerName.properties
import React from 'react';

const PresentationalComponent = (props) => {
  return (
    <div>
      {
        props.reducerName.properties.map(property => {
          return  // always remember to return!
          (
            <div key={property.id}> {property.name} </div>
          )
        })
      }
    </div>
  )
}

/* ---------------<   STATEFUL COMPONENT   >------------------ */
// assumes combineReducers; otherwise use this.props.property
// allows us to manage state and access lifecycle hooks
// REMEMBER TO BIND YOUR FUNCTIONS!
import React from 'react';

class StatefulComponent extends React.Component {
  constructor (props) {
    super(props);
    this.state = {key: value}
    this.rememberToBindYourFunctions = this.rememberToBindYourFunctions.bind(this)
  }

  ComponentDidMount() {
  }

  methodThatChangesState (newValue) {
    this.setState({property: newValue});
  }

  render () {
    return (
      <div>
        {this.props.reducerName.property}
        <ChildComponent Method={this.rememberToBindYourFunctions} />
      </div>
    )
  }
}

/* -------------------<   CONTAINER   >-------------------- */

import { connect } from 'react-redux';

// ES6 syntactic sugar!!
const mapState = ({ propertyNameOrReducerName }) => ({ propertyNameOrReducerName });

// if we need a sync dispatch:
import { loadPuppies } from './actionCreatorsFile';
const mapDispatch = (dispatch) => {
  return {
    methodToDispatch: (params) => dispatch(syncActionCreator(params))
  }
};

// if we have all async thunk dispatchers
const mapDispatch = ({ methodToDispatch }); // react-redux specific syntactic sugar,  enabled by thunk

export default connect(mapState, mapDispatch)(ComponentToReduxify);

Store and Index.js

/* --------------------<   THE STORE   >----------------------- */
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './redux';

export default createStore(
  rootReducer,
  applyMiddleware(thunk)
);

/* -------------------<   browser/INDEX.js   >--------------------- */
// react-redux implementation with react-router
// alternatively, put the react-redux store and ReactDOM.render into a higher level file
'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import { browserHistory, Router, Route, IndexRedirect } from 'react-router';
import { Provider } from 'react-redux';
import store from './store';
import Container from './containers/Container';
import { onLoadFunction } from './redux/actionCreator';

const fetchInitialData = (nextRouterState, replace, done) => {
  store.dispatch(actionCreatorAction(nextRouterState.params.id));
}

ReactDOM.render(
  <Provider store={ store }>
    <Router history={ browserHistory }>
      <Route path="/" onEnter={ fetchInitialData }>
        <IndexRedirect to="/defaultURI" />
        <Route path="/path" component={ Components } />
        <Route path="/path/:id" component={ SingleComponent } onEnter={ doSomething } />
      </Route>
    </Router>
  </Provider>,
  document.getElementById('app')
);

Extra Stuff

Combining Reducers

/* ---------------<   COMBINING REDUCERS  >-------------------- */
import { combineReducers } from 'redux';
import newReducer from './newReducer';
import anotherReducer from './anotherReducer';

export default combineReducers({ newReducer, anotherReducer });

Easy Filtering

filterList (string) {
    return this.props.listToFilter.filter((listItem) => {
      return listItem.name.match(new RegExp(string, 'i'));
    })
  }

Strategies for hiding things:

  • Ternary operators: { condition ? JSXtoShow : null }
  • && Trick: condition && JSXtoShow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment