Directory per View

Each route in your web app will have one directory, and each directory will hold your Redux code. For example, if your app has 3routes such as /home , /trending , /about , then there will be 3directories with the same names as the routes. Additionally, a common directory will contain components that are shared throughout your app, such as buttons, modals, navbars.

+-- /src

+-- /app

+-- /home

+-- /trending

+-- /about

+-- /common

Writing React Components first

I prefer to write the React components first and then the corresponding Redux code. This allows us to have a general understanding of the data requirements.

Presentational and Container Components

One of the most common patterns to organise your React code is to split them into presentational and container components, where containers provide the data needed to render the presentational components. This means that the presentational components don’t have direct access to your Redux store or actions. Instead, they are passed down as props from their containers.

Inside a route’s directory, the presentational components are suffixed with ‘Component’, while containers are suffixed with ‘Container’. The name of the component exported and its file name must be the same.

You may further put these components into respective directories, if the number of components becomes too large. In that case, you can group the components and containers with their corresponding tests.

+-- /src

+-- /app

+-- /home

+-- HomeComponent.jsx

+-- HomeContainer.js

+-- RedditComponent.jsx

+-- RedditContainer.js

I prefer to give the presentational components a .jsx extension while containers get a .js extension, since container components (mostly) do not contain any React code.

Lets write the presentational component RedditComponent first, so that we can have a general idea of what data needs to be passed down to it from its container.

RedditComponent.js

It is good practice to write the presentational components such that they receive data and callbacks exclusively through props. They do not specify how the data is loaded or mutated. We pass it the following props:

subredditData — contains the posts’ text and URLs to their comments pages.

— contains the posts’ text and URLs to their comments pages. showRedditSpinner — can be used to show a loading icon when the posts are being fetched.

— can be used to show a loading icon when the posts are being fetched. fetchSubreddits — will be the callback that triggers the fetching process.

All these will be passed down as arguments from the enclosing container component, which we will write later.

The shortid package is used to generate a unique key for each <li> element generated.

RedditContainer.js

Here, we will import the RedditComponent we just wrote and define the props that we want to pass to it. We will pass relevant data from the store, namely subredditData[Array] and showRedditSpinner[Boolean] using mapStateToProps() . Using mapDispatchToProps() , we also pass in the callback method that will trigger the fetching of data from the Reddit API, namely fetchSubredditJson() .

Lastly, we will use the connect() method to wire everything up with RedditComponent . Note how we do not create the container component ourselves, instead we use the connect() method to generate one.

HomeComponent.jsx

Here we will wire up all the container components together (just RedditContainer in this case). We can also add any presentational code that is too small to be its own component. To add little more complexity to the application, we insert buttons for incrementing and decrementing the count.

Again, this component uses some data ( currentCount ) and callback functions ( onIncrementClick and onDecrementClick ) which get passed as arguments by the wrapping container HomeContainer .

Notice how both presentational components do not care about the data that we pass to it. Their only job is to display whatever is sent to them from their containers.

HomeContainer.js

Similar to RedditContainer , we pass in the count currentCount from the store, and also callback methods onIncrementClick() and onDecrementClick() as props to HomeComponent using the connect() method.

So a quick recap of the components we built so far:

HomeContainer is the highest level component for the /home route.

is the highest level component for the route. HomeComponent , which is HomeContainer ’s child, is where we arrange all the smaller sub-components into a page layout. In our case, it’s just the Reddit component, but a real app will have several such components.

, which is ’s child, is where we arrange all the smaller sub-components into a page layout. In our case, it’s just the Reddit component, but a real app will have several such components. RedditContainer is the wrapper around RedditComponent , and it provides the relevant data and callbacks needed for it’s proper rendering and functioning.

is the wrapper around , and it provides the relevant data and callbacks needed for it’s proper rendering and functioning. RedditComponent is the React component that actually renders the data passed-down from RedditContainer . It does not care about what is passed down, hence isolating it from the logic.

It’s just my preference to write presentational components first, so that I know what data is needed and hence keep the data passed around slim. If a top-down approach makes more sense to you, by all means go ahead.

The Ducks Pattern

Also inside the route’s directory is the duck directory, which houses all your Redux code. The duck directory has a fixed pattern irrespective of the route. We use a modified version of the “ducks” pattern to organise our Redux code:

+-- /src

+-- /app

+-- /home

+-- /duck

+-- types.js

+-- actions.js

+-- operations.js

+-- reducers.js

+-- index.js

+-- tests.js

types.js

This file contains string literals for your action types. This provides an easy reference for the actions available. These strings are exported as an object literal which can then be imported into your reducers and action creators instead of hard-coding them. Although maintaining a separate file containing just your action types is optional, it is highly recommended.

actions.js

This is where you define your action creators. All action creators must be functions that return an object with at least the type property. We do not define any async logic in this file.

A useful tool for writing action creators and reducers is reduxsauce , which considerably reduces the number of lines of code. It also creates string literals for action types by converting your action names into SCREAMING_SNAKE_CASE, so you don’t need to maintain a separate types.js file:

Much shorter and readable right?

operations.js

Here, we define any logic surrounding our actions and side effects, including async logic. If an action has no surrounding logic, then we simply forward them as is, like incrementCount and decrementCount .

reducers.js

The reducer deals with updating the state. We define one reducer for each action in the form of a switch case.

You may also group the reducers as you see fit and use the combineReducers() method to combine them before exporting. I prefer using the createReducer() method provided by reduxsauce to create my reducers. This lets us write our reducers as separate functions instead of one giant switch statement. It also makes the code more readable.

index.js

Here, we re-export our operations, reducers and action types. Note that we do not export our action creators themselves, since we pass them through our operations.

Our duck folder is ready!

Update: You can create a duck directory for src/app as well, where you can define global state and actions. So for example, you can define the app locale in this state.

Whew… Do I still have you? Good, because we still have to wire this data to our React components.

Wire ’em up

We shall define the Redux store in index.js and the app routes in App.jsx . Also, we need to create the root reducer at reducers.js , which combines all the app’s reducers using combineReducers() .

App.jsx

We’re using react-router for routing. We are only defining one route, the /home route.

src/reducers.js

We import all the reducers from all the routes and combine them into a root reducer using combineReducers() . Since we only have only the /home route, we import only its reducer.

src/index.js

Now we create the Redux store using createStore() and connect the app to it using the <Provider> wrapping component. All middleware needs to be imported and applied here.

That’s it!

You don’t have to stick to this pattern throughout the development cycle of your project. Tweak it to your heart’s content and see what works for you and your team. It is, however, important to decide upon and stick to a pattern throughout your project, as though a single person has coded the entire app.

When you organize your code, keep in mind that the tools and libraries you use may change in the future. Your team may want to switch from Redux to some other hot new library, and this process must not be hindered by the fact that your code is all over the place. It is imperative to keep your code as modular as possible, meaning things must be easy to swap in and out.

Cheers :)