The Ducks File Structure for Redux

In the Concur App Center team, we’ve been working with React + Redux for a few months now, having switched over from Flummox. We’ve been bucking the file structure trend in favor of the ducks approach, and have been happy with this decision.

The common way I see of organizing redux is:

|_ containers
|_ constants
|_ reducers
|_ actions

I find this method to be a bit cumbersome. It’s not terrible, but it’s not terribly efficient either. Let’s say we have a reducer that handles all the messaging actions, for example. We have our messaging constants in one file, our messaging reducer in another, our messaging actions in another, and our messaging container in yet another.

Constants and actions/action creators are imported into the reducer file, and action creators are also imported into the container to be dispatched. But while I develop in this setting, I find myself hopping between these files for a single piece of related functionality.

All of this makes developing with Redux, an ecosystem that allows for some very nice design patterns, a little annoying. Our related actions, constants and reducer are in a fractured state, and switching from one file to the next takes a moment to adjust head space and adapt.

This switching and adaptation only takes a few seconds, but when combined it slows down my production efficiency. Instead of following this pattern, my team follows the lesser known Ducks pattern.

So what is Ducks?

Ducks seeks to solve these issues. With ducks, rather than split up all this related code, we package it all in redux modules. So the file structure looks more like this:

|_ containers
|_ modules

Each module contains all of its related constants, actions/action creators, and it’s reducer. If any of our other modules need access to any of these elements (which will likely be the case in the case of a messaging module), we’ll export/import what is needed.

In my experience, there are only a few modules that are needed throughout an app, most are concerned with a specific view or piece of functionality that’s independent from everything else. So we aren’t arbitrarily importing/exporting everything we write (except, of course to, for unit tests).

There are a few rule which are handy to keep in mind while writing your ducks modules. The rules of ducks are as follows, pulled from the Ducks proposal repo:

A module…
1. MUST export default a function called reducer()
2. MUST export its action creators as functions
3. MUST have action types in the form npm-module-or-app/reducer/ACTION_TYPE
4. MAY export its action types as UPPER_SNAKE_CASE, if an external reducer needs to listen for them, or if it is a published reusable library

Here’s an example from the Ducks proposal:

// widgets.js

const LOAD = 'my-app/widgets/LOAD';
const CREATE = 'my-app/widgets/CREATE';
const UPDATE = 'my-app/widgets/UPDATE';
const REMOVE = 'my-app/widgets/REMOVE';

export default function reducer(state = {}, action = {}) {
switch (action.type) {
// do reducer stuff
default: return state;
}
}

export function loadWidgets() {
return { type: LOAD };
}

export function createWidget(widget) {
return { type: CREATE, widget };
}

export function updateWidget(widget) {
return { type: UPDATE, widget };
}

export function removeWidget(widget) {
return { type: REMOVE, widget };
}

And that’s really it. It’s a system that’s worked well for the App Center, and something I can recommend you try out if you’re experiencing similar headaches.