Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS + Redux: How to structure action creators down to each component?

I have one parent component called App.js:

...

render() {
  return (
    <div>
      {React.cloneElement(this.props.children, this.props}
    </div>
  )
}

...

function mapDispatchToProps(dispatch) {
  return (
    actions: bindActionCreators(actions, 
  )
}

export default connect(
  ...,
  mapDispatchToProps
)(App)

And the props would be passed down to each component. I would like to have each component to have its action creators file, but then how can I tie all the action creators into one so that the action creators can be passed down from the App.js level? Any other suggestions would be appreciated too to have action creators down to each component.

Here is the structure so far:

ComponentOne
..actions.js //action creators
..ComponentOne.js
ComponentTwo
..actions.js //action creators
..ComponentTwo.js
App.js
actions.js//should I compile all the action creators here?

And each actions.js would be made like so:

let actions = {
  logSayings() {
    ...
  }
}

export default actions

Thank you in advance and will upvote/accept answer.

REDUX SET UP

store.js

import { applyMiddleware, compose, createStore } from 'redux'
import rootReducer from './reducers/rootReducer'
import logger from 'redux-logger'
import thunk from 'redux-thunk'

let finalCreateStore = compose(
  applyMiddleware(thunk, logger())
)(createStore)

export default function configureStore(initialState = {articles: []}) {
  return finalCreateStore(rootReducer, initialState)
}

actions.js

import { hashHistory } from 'react-router'
import { browserHistory } from 'react-router';

let actions = {
  updateBar(status) {
    return {
      type: 'UPDATE_BAR',
      indicator: status
    }
  }
}

export default actions

homeReducer.js

const homeReducer = function(articles = [], action){
  switch(action.type){
    case 'UPDATE_BAR':
      return {
        indicator: action.indicator,
      }

    default:
      return articles
  }
}

export default homeReducer

index.js

import React from 'react';
import {render} from 'react-dom';
import configureStore from '../../redux/store'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, hashHistory } from 'react-router'

import App from './components/App'
import Home from './components/Home/Home'

let initialState = {

}

let store = configureStore(initialState)

render(
  <div>
    <Provider store={store}>
      <Router history={hashHistory}>
        <Route
          component={App}
          path='/'
        >
          <IndexRoute component={Home}/>
        </Route>
      </Router>
    </Provider>
  </div>,
  document.querySelector('.wrapper')
)
like image 939
Jo Ko Avatar asked Feb 06 '17 21:02

Jo Ko


4 Answers

Here's how I usually structure my react/redux app.

I keep actions outside of the components & containers. I usually try and name my action files specific to areas of the app that I'm building. e.g. UsersActions, ProductActions, OrdersActions, CheeseActions works too...

App
  actions
    CheeseActions.js
    ...
  components
    CheeseBox.js
  containers
    CheeseApp.js
  ...

Example Container - Let the container handle the actions. (Kinda like a controller.)

// import the actions that this container needs to do it's job.
import { makeCheese, sellCheese } from '../actions/CheeseActions'
import CheeseBox from '../components/CheeseBox'

class CheeseApp extends Component {

  // Let the container handle the actions
  onPurchasePressed(kind) {
    dispatch(sellCheese(kind))
  }

  // More actions...

  render() {
    return(
      <CheeseBox onPurchase={this.onPurchasePressed.bind(this)} />
    )
  }

}

...

export default connect(mapStateToProps)(CheeseApp)

Example Component - We're going to let the container handle the function, using this.props.onPurchase('Cheddar').

// Cheesebox is dumb, keep him that way.
export default class CheeseBox extends Component {

  static propTypes = {
    onPurchase: PropTypes.func.isRequired,
  }

  render() {
    const { onPurchase } = this.props
    return(
      <p>Hi, I love cheese.</p>
      <a onClick={() => onPurchase('Cheddar')} />Buy Now!</a>
    )
  }

}

Hopefully this is helpful. Let me know if you have any questions.

like image 52
Brett Avatar answered Nov 11 '22 13:11

Brett


One way to do this is going modular nature wise. This way the code will be cleaner and readable.

App
  action/
    component1Actions.js
    ...
    componentnActions.js
    actionTypes.js
  components/
    Component1.js
    ...
    Componentn.js
  container/
    Component1.js
    ...
    Componentn.js
  reducers/
    component1Reducer.js
    ...
    componentnReducer.js

The structure shown above is what most of the developers I have encountered have used (I prefer this approach). This makes sense because we separate each file based on the nature of that file. This approach is suitable for small to medium projects where there are not that many separate files.

In large apps, it often becomes difficult to maintain the code.

Another school of thought is going domain-wise

app/
  ...
  App.js
  reducers.js
  routes.js
  product/
    Product.js
    ProductContainer.js
    productReducer.js
    ...
  user/
    User.js
    UserContainer.js
    UserActions.js
    userReducer.js
    ...

The benefit of this is that we separate files based on its domain. For e.g. an app needs a User component. It would be easier if we kept all the files related to that domain under one directory. This would make the app structure cleaner in large applications.

Both have benefits. At the end of the day, the structure doesn't matter. It is more of a personal choice.

like image 25
DroidNoob Avatar answered Nov 11 '22 13:11

DroidNoob


I can think about two approaches:

  • Combining your actionObjects all in one at App.js's mapDisatchToProps
  • Each of your components can become a 'container' component

Example 1:

App.js

import actionObj1 from '../actionComponent1'

export default connect(
  mapStateToProps,
  Object.assign({}, actionObj1, actionObj2, actionObj3)
)(App)

UPDATE (each child component become container, just connect like on App.js):

Component1.js

export default connect(
  mapStateToProps,
  actionObj1)
)(Component1)
like image 1
Robsonsjre Avatar answered Nov 11 '22 11:11

Robsonsjre


I think that something that you may want to look at is changing your redux folder structure to be more modular. You wont have bloated action files and it should be easier to maintain than have large action/type/reducer files that encompass all of your redux actions.

I like to separate my redux actions, types, and reducers into modules in the following way:

In my src folder, I will have a modules folder. In this modules folder, I will have sub-folders for different types of data in my application state. For example, a folder for auth, a folder for posts, and a folder for comments. I will also have a index.js.

In each folder, I will have an actions.js file, a types.js file, and a reducer.js file.

In each action.js file, I will only place in actions that pertain to that type of data.

For example, in the actions.js file of my posts folder, I will have the action creators listPosts, getPost, etc. The structure of my action creator is slightly different from what you might see because of the custom middleware that I use, but a great example of a middleware you can use as inspiration or copy is here: react-redux-universal-hot-example. They use a similar style of putting redux items in modules, although they prefer to put them all action creators, types, and reducers in combined files while I like to keep mine separate.

import types from './types';

export const getPost = id => ({
  type: types.LOAD_POST_DETAIL,
  responseTypes: [types.LOAD_POST_DETAIL_SUCCESS, types.LOAD_POST_DETAIL_FAILURE],
  promise: client => client.get(`/posts/${id}`),
});

export const listPosts = () => ({
  type: types.LIST_POSTS,
  responseTypes: [types.LIST_POSTS_SUCCESS, types.LIST_POSTS_FAILURE],
  promise: client => client.get('/posts'),
});

In each types.js file, I will only place in types that pertain to that type of data. For example, my types.js file might look like this:

export default {
  LOAD_POST_DETAIL: 'LOAD_POST_DETAIL',
  LOAD_POST_DETAIL_SUCCESS: 'LOAD_POST_DETAIL_SUCCESS',
  LOAD_POST_DETAIL_FAILURE: 'LOAD_POST_DETAIL_FAILURE',

  LIST_POSTS: 'LIST_POSTS',
  LIST_POSTS_SUCCESS: 'LIST_POSTS_SUCCESS',
  LIST_POSTS_FAILURE: 'LIST_POSTS_FAILURE',

  UPDATE_POST: 'UPDATE_POST',
  UPDATE_POST_SUCCESS: 'UPDATE_POST_SUCCESS',
  UPDATE_POST_FAILURE: 'UPDATE_POST_FAILURE',
};

My reducer will only have reducer functions specific to that type of data.

An example for the reducer.js file in my posts folder:

import { combineReducers } from 'redux';
import type { Reducer } from 'redux';
import { get, assign } from 'lodash';
import types from './types';

const all = (
  state = { isFetching: false, data: [] },
  action,
) => {
  switch (action.type) {
    case types.LIST_POSTS:
      return assign({}, state, { isFetching: true });
    case types.LIST_POSTS_SUCCESS:
      return assign({}, state, get(action, 'result.data'), { isFetching: false });
    default: return state;
  }
};

export const reducer = combineReducers({ all });

In the index.js of the modules folder, you can export all of your reducers (to be combined when you create your redux store) like so:

export { reducer as auth } from './auth/reducer';
export { reducer as posts } from './posts/reducer';
export { reducer as comments } from './comments/reducer';

When you import an action, you would simply do something like

import { listPosts } from 'modules/posts/actions';

Setting up your redux folder structure to have a more modular design can save you a lot of headache since there is a clear pattern to where functions will be placed and where to import your functions from.

like image 1
Yo Wakita Avatar answered Nov 11 '22 13:11

Yo Wakita