React Project Structure

Guide: React Redux Project Structure

To me, one of the things that have the biggest impact on how my ‘developer experience’ will turn out when working on a project is the project structure.

So I’ve been trying out quite a few project structures for React and Redux while developing another React-Redux-Starter-Kit. I’m gonna give you a short taste of what my final React-Start-Kit project structure will look like. The problem when using React and Redux is that it tends to be quite a few folders, especially when splitting up action creators, containers, constants, components and reducers.

tl;dr

So you just want the good parts without having to read my post? Fine, here it is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
├── client
│   ├── components
│   │   ├── App
│   │   │   ├── app.spec.js
│   │   │   └── index.js
│   │   └── Grid
│   │       ├── Column.js
│   │       ├── Container.js
│   │       ├── grid.spec.js
│   │       ├── index.js
│   │       ├── Row.js
│   │       └── styles.scss
│   ├── config
│   │   └── api.js
│   ├── features
│   │   ├── App
│   │   │   ├── index.js
│   │   │   └── reducer.js
│   │   └── store.js
│   ├── styles
│   │   ├── defaults.scss
│   │   ├── mixins.scss
│   │   └── variables.scss
│   ├── test
│   │   └── helpers
│   │       ├── dom.js
│   │       ├── globals.js
│   │       └── ignores.js
│   ├── vendor
│   │   └── axios.js
│   ├── views
│   │   ├── Home
│   │   │   ├── GridSystem.js
│   │   │   └── index.js
│   │   └── Login
│   └── app.js
├── public
│   ├── bundle.css
│   ├── bundle.js
│   ├── index.html
│   ├── styles.css
├── mocha.opts
├── package.json
└── webpack.config.js

Components

A standard components directory, all folders should have an Capital case letter with an index.js file exporting the component(s) that the folder represents. By keeping Capital names in our React Redux Project structure it’s easier to perform our imports. I also always use aliases in Webpack to make it easier to move around (and change names) of our folder.

1
2
3
4
5
6
7
8
9
10
11
components
├── App
│   ├── app.spec.js
│   └── index.js
└── Grid
    ├── Column.js
    ├── Container.js
    ├── grid.spec.js
    ├── index.js
    ├── Row.js
    └── styles.scss

Rules

  • Webpack alias: ‘components’ for this directory.
  • All folder names use a Capital case.
  • All components have a test-file, named [component].spec.js
  • All components that are ‘public’ are exported via the index.js file.
  • All components use CSS-modules and takes care of their own ‘style’.
  • Private components (not exported by index.js) can only be used inside their own folder.
  • Components does not contain any logic. (see containers)

With Webpack aliases we can make our imports very clear, below is an example of how we import all the items

1
2
import { Container, Row, Column } from 'components/Grid';
import App from 'components/App';

Config

Here I keep all the configuration options. The configuration usually exports different values depending on the environment I’m running (production/development). This is also ‘aliased’ up via webpack to give us easier imports.

Rules

  • Webpack alias: ‘config’ for this directory
  • Typically keeps configurations depending on environment.
1
import api from 'config/api';

Features

I want to reduce the number of folders and I did that by combining the actions, constants and reducer into a single file named reducer.js. The index.js file exports the ‘container’ via react-redux connect(). A reducer file default-exports reducer function for import to the store. (of-course we also use Webpack aliases here with the name ‘features’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export const types = {
    START_LOADING: 'react-alpha/app/START_LOADING'
};

export const INITIAL_STATE = {
    loading: false
};

export default (state = INITIAL_STATE, action) => {
    switch (action.state) {
    case types.START_LOADING:
        return { ...state, loading: true };
    default:
        return state;
    }
};

export const actions = {
    startLoading: () => ({ type: types.START_LOADING })
};

The index.js exports the ‘connected’ component so that we can import it easily.

1
2
3
4
5
6
7
8
9
10
11
12
import { connect } from 'react-redux';
import App from 'components/App';
import { actions } from 'features/App/reducer';

export default connect(
    state => ({
        isLoading: state.app.loading
    }),
    dispatch => ({
        onButtonClick: dispatch(actions.startLoading())
    })
)(App);

And when we want to import our feature, since we are using Webpack aliases it would look likes this:

1
import App from 'features/App';

Store

So the store is placed in the root of our features directory. This is where we create our store so that we can just import it to the app.js (entry point). The store file in turn imports all our reducers, using redux-thunk and redux-devtools the file looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createStore,
         combineReducers,
         applyMiddleware,
         compose
    } from 'redux';
import thunk from 'redux-thunk';

import App from 'features/App/reducer';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export default createStore(
    combineReducers({
        App
    }),
    composeEnhancers(applyMiddleware(thunk))
);

Rules

  • Webpack alias: ‘features’ for this directory
  • Feature folders have Capital case names
  • index.js exports the container.
  • reudcer.js exports the action creators, constants and reducer.
  • store.js imports all the reducers, creates the store and exports it.

Styles

This directory contains the ‘default’ styles that apply everywhere. This folder is setup using alias ‘styles’, so that we can @import '~styles/mixins' from our .scss files that’s put next to our components. It’s important that we keep all our variables in the variables.scss file so that we can handle the layout of our components using one source of data.

Sample mixins.scss file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@import '~styles/variables';
%clearfix:after {
    display: block;
    clear: both;
    content: '';
}

@mixin small {
    @content;
}
@mixin medium {
    @media (min-width: #{$mediumWidth}) {
        @content;
    }
}
@mixin large {
    @media (min-width: #{$largeWidth}) {
        @content;
    }
}

Rules

  • Webpack alias: ‘styles’ for this directory
  • Contains our default styles, mixins and variables for scss.
  • defaults.scss is imported in our entry file and does not contain any #id or .classes.

Test

Folder that contains test data and fixtures/helpers if needed. This folder can contain items that we want to import so we are using a webpack alias ‘test’.

Rules

  • Webpack alias: ‘test’ for this directory
  • Contains fixtures/helpers for testing purposes.

Vendor

Contains our vendors. The vendors directory is used when we want to use a configured import, for instance axios where we can initialize it with some configuration options depending on our environment before we export it.

Rules

  • Webpack alias: ‘vendors’ for this directory
  • Exports pre-configured vendors used in the app.

Views

This is where our features and components come together to build a page. A view typically have it’s own route and is imported asynchronously via wepback import() functionality to keep our bundle.js small. Components in the view is written in Capital Letters.

Rules

  • Webpack alias: ‘views’ for this directory
  • Each view represents a route.

Public

Our public directory is where our webpack outputs our bundle and html. It has no webpack alias since we do not import anything from this directory. Our image-files are kept in the separate component/view directories.

App.js

This is where it all comes together, this is where we start our rendering and it’s our entry point for Webpack. We also include the Redux store here exported from features. We import our defaults.scss from our styles directory here to apply our general css-rules everywhere.

1
2
3
4
5
6
7
8
9
10
11
12
import App from 'components/App';
import { Provider } from 'react-redux';
import store from 'features/store';

import 'styles/defaults.scss';

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('app')
);

That’s it!

This is the folder structure I’m currently working with and it definitely fits my development style. The react-starter-kit I’m working on will use this structure but will also handle selectors for redux somewhere, I haven’t decided where to put them yet but the features directory is a possible contender at the moment. How do you structure your projects with React Redux (and selectors?)

Summary of Rules

Components

  • Webpack alias: ‘components’ for this directory.
  • All folder names use a Capital case.
  • All components have a test-file, named [component].spec.js
  • All components that are ‘public’ are exported via the index.js file.
  • All components use CSS-modules and takes care of their own ‘style’.
  • Private components (not exported by index.js) can only be used inside their own folder.
  • Components does not contain any logic. (see containers)

Config

  • Webpack alias: ‘config’ for this directory
  • Typically keeps configurations depending on environment.

Features

  • Webpack alias: ‘features’ for this directory
  • Feature folders have Capital case names
  • index.js exports the container.
  • reudcer.js exports the action creators, constants and reducer.
  • store.js imports all the reducers, creates the store and exports it.

Styles

  • Webpack alias: ‘styles’ for this directory
  • Contains our default styles, mixins and variables for scss.
  • defaults.scss is imported in our entry file and does not contain any #id or .classes.

Test

  • Webpack alias: ‘test’ for this directory
  • Contains fixtures/helpers for testing purposes.

Vendor

  • Webpack alias: ‘vendors’ for this directory
  • Exports pre-configured vendors used in the app.

Views

  • Webpack alias: ‘views’ for this directory
  • Each view represents a route.
Please follow and like SnappyJS:

One thought on “Guide: React Redux Project Structure

Leave a Reply

Your email address will not be published.