{"id":13808871,"url":"https://github.com/alexnm/re-ducks","last_synced_at":"2025-04-04T23:07:59.339Z","repository":{"id":90942812,"uuid":"76064019","full_name":"alexnm/re-ducks","owner":"alexnm","description":"An attempt to extend the original proposal for redux modular architecture: https://github.com/erikras/ducks-modular-redux","archived":false,"fork":false,"pushed_at":"2018-01-17T08:21:58.000Z","size":125,"stargazers_count":886,"open_issues_count":2,"forks_count":60,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-10-14T09:24:56.044Z","etag":null,"topics":["duck","folders","react","redux"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexnm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-12-09T19:26:27.000Z","updated_at":"2024-07-04T13:08:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"e495c203-3cfd-4721-8910-5747184f234c","html_url":"https://github.com/alexnm/re-ducks","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexnm%2Fre-ducks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexnm%2Fre-ducks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexnm%2Fre-ducks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexnm%2Fre-ducks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexnm","download_url":"https://codeload.github.com/alexnm/re-ducks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247261604,"owners_count":20910108,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["duck","folders","react","redux"],"created_at":"2024-08-04T01:01:53.952Z","updated_at":"2025-04-04T23:07:59.319Z","avatar_url":"https://github.com/alexnm.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Building on the duck legacy\nBefore starting, read more about the original [ducks modular approach proposal](https://github.com/erikras/ducks-modular-redux). When trying to embrace this idea in medium-large scale codebases I noticed that the single duck file becomes harder and harder to maintain and read.\n\nSo I want to propose an extended approach that works great when you go beyond a todo-app.\n\nTo recap, a duck:\n* MUST `export default` a function called `reducer()`\n* MUST `export` its action creators as functions\n* MUST have action types in the form `npm-module-or-app/reducer/ACTION_TYPE`\n* 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\n\n## Enter re-ducks\nInstead of duck files, we use duck folders.\n\nHere's how a **duck** folder would look like:\n```\nduck/\n├── actions.js\n├── index.js\n├── operations.js\n├── reducers.js\n├── selectors.js\n├── tests.js\n├── types.js\n├── utils.js\n```\nNOTE: Each concept from your app will have a similar folder.\n\n## Codebase examples\n[React/Redux ecommerce example](https://github.com/FortechRomania/react-redux-complete-example) - WIP\n\n[React/Redux TodoApp + Flow](https://github.com/jthegedus/re-ducks-examples)\n\n## General rules for a duck folder\nA duck folder:\n* MUST contain the **entire logic** for handling **only ONE** concept in your app, ex: product, cart, session, etc.\n* MUST have an `index.js` file that exports according to the original duck rules.\n* MUST keep code with similar purpose in the same file, ex: reducers, selectors, actions, etc.\n* MUST contain the **tests** related to the duck.\n\nThis structure does not require any libraries or abstractions other than `redux` and `redux-thunk` at a minimum (but can work with `redux-saga` or `redux-observables`)\n\nNOTE: I'm using `export default` in most of the cases, don't want to get into details there, you can compose your module as you wish.\n\n### Types\nLet's start from defining the constants we will use as redux action types. In order to keep the naming simple, let's call the file `types.js`, because `constants.js` is a bit too generic.\n\nOur examples will model a real duck.\n```javascript\nconst QUACK = \"app/duck/QUACK\";\nconst SWIM = \"app/duck/SWIM\";\n\nexport {\n    QUACK,\n    SWIM\n};\n```\n\n### Actions\nIt's important to be consistent when defining actions, so let's always export functions from this file, we don't care if the action needs any input from the outside to build the payload or not.\n```javascript\nimport * as types from \"./types\";\n\nconst quack = ( ) =\u003e ( {\n    type: types.QUACK\n} );\n\nconst swim = ( distance ) =\u003e ( {\n    type: types.SWIM,\n    payload: {\n        distance\n    }\n} );\n\nexport {\n    swim,\n    quack\n};\n```\nNOTE: Trying to impose a bit of structure to the actions object, the `type/payload` approach is pretty popular.\n\n### Operations\nIn a simple application, you can easily dispatch simple actions and use the reducers to manage the state. However, in a more complex app you need to use some sort of middleware to handle more complex interactions. In our case, we use [redux-thunk](https://github.com/gaearon/redux-thunk).\n\nThe operations file define the `interface` for our duck. You can reason about it like this: 1 operation = X actions dispatched. This makes each operation function either **a thunk** in case it needs to dispatch multiple actions, or simply **a link** to an action already defined in `actions.js`.\n\nThis separation should work with whatever middleware/lib you are using for handling chained/linked/delayed operations.\n```javascript\nimport * as actions from \"./actions\";\n\n// This is a link to an action defined in actions.js.\nconst simpleQuack = actions.quack;\n\n// This is a thunk which dispatches multiple actions from actions.js\nconst complexQuack = ( distance ) =\u003e ( dispatch ) =\u003e {\n    dispatch( actions.quack( ) ).then( ( ) =\u003e {\n        dispatch( actions.swim( distance ) );\n        dispatch( /* any action */ );\n    } );\n}\n\nexport {\n    simpleQuack,\n    complexQuack\n};\n```\nNOTE: [redux-observables](https://github.com/redux-observable/redux-observable) uses the idea of `epics` which I also like. Feel free to suggest names for this file by dropping a line on **[twitter](https://twitter.com/alexnmoldovan)**.\n\n### Reducers\nIt's a good practice to keep your **state shape** in a comment above the reducers, just to have an overview.\n\nIn case the state shape is more complex, you should break the reducers into multiple smaller functions that deal with a slice of the state, then combine them at the end.\n```javascript\nimport { combineReducers } from \"redux\";\nimport * as types from \"./types\";\n\n/* State Shape\n{\n    quacking: bool,\n    distance: number\n}\n*/\n\nconst quackReducer = ( state = false, action ) =\u003e {\n    switch( action.type ) {\n        case types.QUACK: return true;\n        /* ... */\n        default: return state;\n    }\n}\n\nconst distanceReducer = ( state = 0, action ) =\u003e {\n    switch( action.type ) {\n        case types.SWIM: return state + action.payload.distance;\n        /* ... */\n        default: return state;\n    }\n}\n\nconst reducer = combineReducers( {\n    quacking: quackReducer,\n    distance: distanceReducer\n} );\n\nexport default reducer;\n```\nNOTE: Let's keep it simple for now with `switch` statements and abstract later.\n\n### Selectors\nIn case your state shape is more complex you need selectors in order to map parts of the `state` to your `props` or in order to derive some data for your components from the current state.\n\nThese are the functions like: `getVisibleTodos`, `isUserAuthenticated`, etc. that take the current app state and return some derived data.\n```javascript\nfunction checkIfDuckIsInRange( duck ) {\n    return duck.distance \u003e 1000;\n}\n\nexport {\n    checkIfDuckIsInRange\n};\n```\nNOTE: Selector functions will be used outside the duck folder, so they are part of the **interface** of the duck.\n\n### Index\nThis file, from a module perspective, behaves as the duck file from the original proposal.\n* It exports, as default, the reducer function of the duck.\n* It exports, as named export, the selectors and the operations.\n* Optionally, it exports the types if they are needed in other ducks.\n```javascript\nimport reducer from \"./reducers\";\n\nimport * as duckSelectors from \"./selectors\";\nimport * as duckOperations from \"./operations\";\nimport * as duckTypes from \"./types\";\n\nexport {\n    duckSelectors,\n    duckOperations,\n    duckTypes\n};\n\nexport default reducer;\n```\n\n### Tests\nOne of the main advantages of `redux` is that you can easily do unit tests for your `reducers`, `action creators` and `selectors`. And with a small effort, you can do the same for the `operations`.\n\nUltimately, the split proposed here also helps you see what you need to test inside each duck. This example is using `mocha` and `expect.js`.\n```javascript\nimport expect from \"expect.js\";\nimport reducer from \"./reducers\";\nimport * as actions from \"./actions\";\n\ndescribe( \"duck reducer\", function( ) {\n    describe( \"quack\", function( ) {\n        const quack = actions.quack( );\n        const initialState = false;\n\n        const result = reducer( initialState, quack );\n\n        it( \"should quack\", function( ) {\n            expect( result ).to.be( true ) ;\n        } );\n    } );\n} );\n```\n\n### A word on abstractions!\nI've been working almost 2 years now in the React ecosystem and I found that abstractions are good when you design them for your specific needs and are your worst enemies when you use them blindly. So while I understand the idea of reducing boilerplate, writing less code, reusing structures, keep in mind that any abstraction that you take for granted will eventually become a burden for your project.\n\nWhat I like about the `ducks approach` is that it does not enforce any abstraction on you. It's simply a better way of organizing your app. You have the complete freedom to build your own helper functions or use utility packages that have those functions.\n\n=======================================\n\nRead more about [the reasoning behind re-ducks](https://medium.com/@alexnm/scaling-your-redux-app-with-ducks-6115955638be#.4ppptx7oq)\n\nHope you find something useful in this! Ping me on [twitter](https://twitter.com/alexnmoldovan), i'd be more than happy to hear your thoughts!\n\nAlex M\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexnm%2Fre-ducks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexnm%2Fre-ducks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexnm%2Fre-ducks/lists"}