Full Code of erikras/ducks-modular-redux for AI

master 60390d09e6c5 cached
5 files
11.7 KB
3.0k tokens
10 symbols
1 requests
Download .txt
Repository: erikras/ducks-modular-redux
Branch: master
Commit: 60390d09e6c5
Files: 5
Total size: 11.7 KB

Directory structure:
gitextract_x08mfb1r/

├── CommonJs.md
├── README.md
└── battlecry/
    └── generators/
        └── duck/
            ├── duck.generator.js
            └── templates/
                ├── __naMe__.js
                └── configureStore.js

================================================
FILE CONTENTS
================================================

================================================
FILE: CommonJs.md
================================================
## Common JS Example

```javascript
// 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';

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

reducer.loadWidgets = function() {
  return { type: LOAD };
}

reducer.createWidget = function(widget) {
  return { type: CREATE, widget };
}

reducer.updateWidget = function(widget) {
  return { type: UPDATE, widget };
}

reducer.removeWidget = function(widget) {
  return { type: REMOVE, widget };
}

module.exports = reducer;
```


One of the different caveats is that you can't use Redux' `bindActionCreators()` directly with a duck module, as it assumes that when given a function, it's a single action creator, so you need to do something like:

```javascript
var actionCreators = require('./ducks/widgets');
bindActionCreators({ ...actionCreators });
```

Another is that if you're also exporting some type constants, you need to attach those to the reducer function too, so you can't unpack just the action creators into another object at import time as easily (no `as` syntax) so the above trick isn't as viable.

You can avoid getting bitten by both of these by rolling your own dispatch binding function - this is the one I'm using to create a function to be passed as the `mapDispatchToProps` argument to `connect()`:

```javascript
/**
 * Creates a function which creates same-named action dispatchers from an object
 * whose function properties are action creators. Any non-functions in the actionCreators
 * object are ignored.
 */
var createActionDispatchers = actionCreators => dispatch =>
  Object.keys(actionCreators).reduce((actionDispatchers, name) => {
    var actionCreator = actionCreators[name];
    if (typeof actionCreator == 'function') {
      actionDispatchers[name] = (...args) => dispatch(actionCreator(...args));
    }
    return actionDispatchers;
  }, {})

var actionCreators = require('./ducks/widgets');
var mapStateToProps = state => state.widgets;
var mapDispatchToProps = createActionDispatchers(actionCreators);

var MyComponent = React.createClass({ /* ... */ });

module.exports = connect(mapStateToProps , mapDispatchToProps)(MyComponent);
```

---
This document copied almost verbatim from [@insin](https://github.com/insin)'s issue [#2](https://github.com/erikras/ducks-modular-redux/issues/2).

================================================
FILE: README.md
================================================
<p align="center">
<a href="https://codefund.io/properties/555/visit-sponsor">
<img src="https://codefund.io/properties/555/sponsor" />
</a>
</p>
    
# Ducks: Redux Reducer Bundles

<img src="duck.jpg" align="right"/>

I find as I am building my redux app, one piece of functionality at a time, I keep needing to add  `{actionTypes, actions, reducer}` tuples for each use case. I have been keeping these in separate files and even separate folders, however 95% of the time, it's only one reducer/actions pair that ever needs their associated actions.

To me, it makes more sense for these pieces to be bundled together in an isolated module that is self contained, and can even be packaged easily into a library.

## The Proposal

### Example

See also: [Common JS Example](CommonJs.md).

```javascript
// widgets.js

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

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

// Action Creators
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 };
}

// side effects, only as applicable
// e.g. thunks, epics, etc
export function getWidget () {
  return dispatch => get('/widget').then(widget => dispatch(updateWidget(widget)))
}

```
### Rules

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`
3. 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

These same guidelines are recommended for `{actionType, action, reducer}` bundles that are shared as reusable Redux libraries.

### Name

Java has jars and beans. Ruby has gems. I suggest we call these reducer bundles "ducks", as in the last syllable of "redux".

### Usage

You can still do:

```javascript
import { combineReducers } from 'redux';
import * as reducers from './ducks/index';

const rootReducer = combineReducers(reducers);
export default rootReducer;
```

You can still do:

```javascript
import * as widgetActions from './ducks/widgets';
```
...and it will only import the action creators, ready to be passed to `bindActionCreators()`.

> Actually, it'll also import `default`, which will be the reducer function. It'll add an action creator named `default` that won't work. If that's a problem for you, you should enumerate each action creator when importing.

There will be some times when you want to `export` something other than an action creator. That's okay, too. The rules don't say that you can *only* `export` action creators. When that happens, you'll just have to enumerate the action creators that you want. Not a big deal.

```javascript
import {loadWidgets, createWidget, updateWidget, removeWidget} from './ducks/widgets';
// ...
bindActionCreators({loadWidgets, createWidget, updateWidget, removeWidget}, dispatch);
```

### Example

[React Redux Universal Hot Example](https://github.com/erikras/react-redux-universal-hot-example) uses ducks. See [`/src/redux/modules`](https://github.com/erikras/react-redux-universal-hot-example/tree/master/src/redux/modules).

[Todomvc using ducks.](https://github.com/goopscoop/ga-react-tutorial/tree/6-reduxActionsAndReducers)

### BattleCry generators

There are configurable [BattleCry](https://github.com/pedsmoreira/battlecry) generators ready to be downloaded and help scaffolding ducks:

```sh
npm install -g battlecry
cry download generator erikras/ducks-modular-redux
cry init duck
```

Run `cry --help` to check more info about the generators available;

### Implementation

The migration to this code structure was [painless](https://github.com/erikras/react-redux-universal-hot-example/commit/3fdf194683abb7c40f3cb7969fd1f8aa6a4f9c57), and I foresee it reducing much future development misery.

Although it's completely feasable to implement it without any extra library, there are some tools that might help you:

 * [extensible-duck](https://github.com/investtools/extensible-duck) - Implementation of the Ducks proposal. With this library you can create reusable and extensible ducks.
 * [saga-duck](https://github.com/cyrilluce/saga-duck) - Implementation of the Ducks proposal in Typescript with [sagas](https://github.com/redux-saga/redux-saga) in mind. Results in reusable and extensible ducks.
 * [redux-duck](https://github.com/PlatziDev/redux-duck) - Helper function to create Redux modules using the ducks-modular-redux proposal
 * [modular-redux-thunk](https://github.com/benbeadle/modular-redux-thunk) - A ducks-inspired package to help organize actions, reducers, and selectors together - with built-in redux-thunk support for async actions.
 * [molecular-js](https://www.npmjs.com/package/molecular-js) - Set of utilities to ease the development of modular state management patterns with Redux (also known as ducks).
 * [ducks-reducer](https://github.com/drpicox/ducks-reducer) - Function to combine _ducks object_ reducers into one reducer (equivalent to [combineReducers](https://redux.js.org/docs/api/combineReducers.html)), and function [ducks-middleware](https://github.com/drpicox/ducks-middleware) to combine _ducks object_ middleware into one single middleware compatible with [applyMiddleware](https://redux.js.org/docs/api/applyMiddleware.html).
 * [simple-duck](https://github.com/xander27/simple-duck) - Class based implementation of modules system, inspired by ducks-modular-redux. All OOP benefits like inheritance and composition. Support combining of duck-module classes and regular reducer functions using `combineModules` function.

Please submit any feedback via an issue or a tweet to [@erikras](https://twitter.com/erikras). It will be much appreciated.

Happy coding!

-- Erik Rasmussen


### Translation

[한국어](https://github.com/JisuPark/ducks-modular-redux)
[中文](https://github.com/deadivan/ducks-modular-redux)
[Türkçe](https://github.com/mfyz/ducks-modular-redux-tr)

---

![C'mon! Let's migrate all our reducers!](migrate.jpg)
> Photo credit to [Airwolfhound](https://www.flickr.com/photos/24874528@N04/3453886876/).

---

[![Beerpay](https://beerpay.io/erikras/ducks-modular-redux/badge.svg?style=beer-square)](https://beerpay.io/erikras/ducks-modular-redux)  [![Beerpay](https://beerpay.io/erikras/ducks-modular-redux/make-wish.svg?style=flat-square)](https://beerpay.io/erikras/ducks-modular-redux?focus=wish)


================================================
FILE: battlecry/generators/duck/duck.generator.js
================================================
import { Generator, File, namedCasex, casex, log } from 'battlecry';

const CONFIG_FILE = 'configureStore.js';
const REDUX_PATH = 'src/redux';

export default class DuckGenerator extends Generator {
  config = { 
    init: {
      description: 'Create configStore.js file and an example duck'
    },
    generate: {
      args: 'name ...actions?',
      description: 'Create or modify duck to add actions'
    }
  };

  get configFile() {
    const template = this.template(CONFIG_FILE);
    const path = `${REDUX_PATH}/${template.filename}`;

    return new File(path);
  }

  get actions() {
    return (this.args.actions || ['set']).reverse();
  }

  init() {
    const configFile = this.configFile;
    if(configFile.exists) return log.warn(`Modular ducks have already been initiated. Please check the ${configFile.path} file`);

    this.template(CONFIG_FILE).saveAs(configFile.path);
    this.generator('duck').setArgs({name: 'todo'}).play('generate');
  }

  generate() {
    this.addActionsToDuck();
    this.addDuckToConfig();
  }

  addActionsToDuck() {
    const template = this.template('_*');
    const path = `${REDUX_PATH}/modules/${template.filename}`;

    let file = new File(path, this.args.name);
    if(!file.exists) file = template;
    
    this.actions.forEach(action => {
      file.after('// Actions', `const __NA_ME__ = '${casex(this.args.name, 'na-me')}/__NA-ME__';`, action);
      
      file.after('switch (action.type) {', [
        '    case __NA_ME__:',
        '      // Perform action',
        '      return state;'
      ], action);
      
      file.after('// Action Creators', [
        namedCasex('export function __naMe__() {', + `${action}_${this.args.name}`),
        '  return { type: __NA_ME__ };',
        '}',
        ''
      ], action);
    });

    file.saveAs(path, this.args.name);
  }

  addDuckToConfig() {
    const file = this.configFile;
    if(!file.exists) return null;

    file
      .afterLast('import ', "import __naMe__ from './modules/__naMe__'", this.args.name)
      .after('combineReducers({', '  __naMe__,', this.args.name)
      .save();
  }
}


================================================
FILE: battlecry/generators/duck/templates/__naMe__.js
================================================
// Actions

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

// Action Creators

================================================
FILE: battlecry/generators/duck/templates/configureStore.js
================================================
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createLogger from 'redux-logger';

const loggerMiddleware = createLogger(); // initialize logger

const createStoreWithMiddleware = applyMiddleware(loggerMiddleware)(createStore); // apply logger to redux

const reducer = combineReducers({
});

const configureStore = (initialState) => createStoreWithMiddleware(reducer, initialState);
export default configureStore;
Download .txt
gitextract_x08mfb1r/

├── CommonJs.md
├── README.md
└── battlecry/
    └── generators/
        └── duck/
            ├── duck.generator.js
            └── templates/
                ├── __naMe__.js
                └── configureStore.js
Download .txt
SYMBOL INDEX (10 symbols across 2 files)

FILE: battlecry/generators/duck/duck.generator.js
  constant CONFIG_FILE (line 3) | const CONFIG_FILE = 'configureStore.js';
  constant REDUX_PATH (line 4) | const REDUX_PATH = 'src/redux';
  class DuckGenerator (line 6) | class DuckGenerator extends Generator {
    method configFile (line 17) | get configFile() {
    method actions (line 24) | get actions() {
    method init (line 28) | init() {
    method generate (line 36) | generate() {
    method addActionsToDuck (line 41) | addActionsToDuck() {
    method addDuckToConfig (line 68) | addDuckToConfig() {

FILE: battlecry/generators/duck/templates/__naMe__.js
  function reducer (line 4) | function reducer(state = {}, action = {}) {
Condensed preview — 5 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (13K chars).
[
  {
    "path": "CommonJs.md",
    "chars": 2477,
    "preview": "## Common JS Example\n\n```javascript\n// widgets.js\n\nconst LOAD   = 'my-app/widgets/LOAD';\nconst CREATE = 'my-app/widgets/"
  },
  {
    "path": "README.md",
    "chars": 6830,
    "preview": "<p align=\"center\">\n<a href=\"https://codefund.io/properties/555/visit-sponsor\">\n<img src=\"https://codefund.io/properties/"
  },
  {
    "path": "battlecry/generators/duck/duck.generator.js",
    "chars": 2115,
    "preview": "import { Generator, File, namedCasex, casex, log } from 'battlecry';\n\nconst CONFIG_FILE = 'configureStore.js';\nconst RED"
  },
  {
    "path": "battlecry/generators/duck/templates/__naMe__.js",
    "chars": 159,
    "preview": "// Actions\n\n// Reducer\nexport default function reducer(state = {}, action = {}) {\n  switch (action.type) {\n    default: "
  },
  {
    "path": "battlecry/generators/duck/templates/configureStore.js",
    "chars": 444,
    "preview": "import { createStore, applyMiddleware, combineReducers } from 'redux';\nimport createLogger from 'redux-logger';\n\nconst l"
  }
]

About this extraction

This page contains the full source code of the erikras/ducks-modular-redux GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 5 files (11.7 KB), approximately 3.0k tokens, and a symbol index with 10 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!