Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/iamogbz/react-ducks
🦆 Ducks architecture using native react features without redux framework
https://github.com/iamogbz/react-ducks
immer react react-ducks react-hooks react-redux react-redux-tutorial redux redux-applymiddleware
Last synced: 2 months ago
JSON representation
🦆 Ducks architecture using native react features without redux framework
- Host: GitHub
- URL: https://github.com/iamogbz/react-ducks
- Owner: iamogbz
- License: unlicense
- Created: 2020-09-03T03:20:58.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2024-05-01T10:42:32.000Z (8 months ago)
- Last Synced: 2024-05-02T01:37:16.022Z (8 months ago)
- Topics: immer, react, react-ducks, react-hooks, react-redux, react-redux-tutorial, redux, redux-applymiddleware
- Language: TypeScript
- Homepage: https://ogbizi.com/react-ducks/
- Size: 67.5 MB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# React Ducks
[![NPM badge](https://img.shields.io/npm/v/react-ducks)](https://www.npmjs.com/package/react-ducks)
[![Dependabot badge](https://badgen.net/github/dependabot/iamogbz/react-ducks/?icon=dependabot)](https://app.dependabot.com)
[![Dependencies](https://img.shields.io/librariesio/github/iamogbz/react-ducks)](https://libraries.io/github/iamogbz/react-ducks)
[![Build Status](https://github.com/iamogbz/react-ducks/workflows/Build/badge.svg)](https://github.com/iamogbz/react-ducks/actions)
[![Coverage Status](https://coveralls.io/repos/github/iamogbz/react-ducks/badge.svg?branch=master)](https://coveralls.io/github/iamogbz/react-ducks?branch=master)Implement ducks in React following the redux pattern but using React Context.
Uses [`immer`][immer-intro] to wrap reducers when creating, ensuring atomic state mutations.
## Usage
Create the ducks for each slice of application logic.
```js
// duck/counter.js
export default createDuck({
name: "counter",
initialState: 0,
reducers: {
increment: (state) => state + 1,
},
actionMapping: { otherActionType: "increment" },
selectors: { current: (namespacedState) => namespacedState["counter"] },
});
```**Note**: The `current` selector is just an example. In an actual implementation it would be made redundant by `$` which is created by default for all ducks to fetch the namespaced state.
```js
counterDuck.selectors.$(state);
// this is equivalent to (rootState) => rootState[counterDuck.name]
```Create the root/global duck as a combination of all other ducks.
```js
// duck/index.js
export default createRootDuck(counterDuck, otherDuck);
```Create the global context.
```js
// context.js
export default createContext(
rootDuck.reducer,
rootDuck.initialState,
enhancer,
"ContextName",
useAsGlobalContext
);
```**Note**: The `enhancer` may be optionally specified to enhance the context with third-party capabilities such as middleware, time travel, persistence, etc. The only context enhancer that ships with Ducks is [applyMiddleware](#applyMiddlewaremiddlewares).
**Note**: The `useAsGlobalContext` i.e. `global` option; allows for setting a default context that is used by the [`useDispatch`](#useDispatchactionCreatorContext) and [`useSelector`](#useSelectorselectorContext) hooks when no `Context` is supplied. This is useful when creating the context that will be used with the root provider.
Use the state and actions in your component.
```jsx
// app.jsx
export default function App(props) {
const { state, dispatch } = React.useContext(Context);
const count = counterDuck.selectors.$(state);
const increment = React.useCallback(
() => dispatch(counterDuck.actions.increment()),
[dispatch]
);
return (
Count: {count}
);
}
```**Note**: The use of `React.useContext` can be replaced with a combination of [`useDispatch`](#useDispatchactionCreatorContext) and [`useSelector`](#useSelectorselectorContext) hooks.
```jsx
// app.jsx
...
const count = useSelector(counterDuck.selectors.$, Context);
const increment = useDispatch(counterDuck.actions.increment, Context);
...
```**Note**: This is equivalent to the class component described below.
```jsx
// app.jsx
export default class App extends React.PureComponent {
static contextType = Context;render() {
const { state } = this.context;
return (
Count: {counterDuck.selectors.$(state)}
);
}increment = () => {
this.context.dispatch(counterDuck.actions.increment());
};
}
```Wrap the application in the root provider to handle state changes.
```js
// index.jsx
const rootElement = document.getElementById("root");
const Provider = createRootProvider(Context);
ReactDOM.render(
,
rootElement
);
```**Note**: `createRootProvider` is just a helper and can be replaced, with the functional difference highlighted below.
```js
// index.jsx
const rootElement = document.getElementById("root");
ReactDOM.render(
...
```A side benefit to scoping the context state to the provider is allowing multiple entire apps to be run concurrently.
### applyMiddleware(...middlewares)
This takes a variable list of middlewares to be applied.
#### Example: Custom Logger Middleware
```js
// context.js
function logger({ getState }) {
// Recommend making the returned dispatch method asynchronous.
return (next) => async (action) => {
console.log("will dispatch", action);
// Call the next dispatch method in the middleware chain.
const returnValue = await next(action);
// Resolving the result of the next dispatch allows the referenced
// state to be updated by `React.useReducer` and available to get.
console.log("state after dispatch", getState());
// This will likely be the action itself, unless
// a middleware further in chain changed it.
return returnValue;
};
}export default createContext(..., applyMiddleware(logger));
```See [redux applyMiddleware][redux-applymiddleware] for more documentation.
## Demo
As a proof of concept see the converted sandbox app from the react-redux basic tutorial below.
- With redux [example][react-redux-tutorial]
- Without redux [example][react-ducks-no-redux]
- With redux saga [example][react-ducks-saga]## References
Lots of inspiration from the following tools
- [Redux Toolkit][redux-toolkit]
- [React Redux][react-redux][immer-intro]: https://medium.com/hackernoon/introducing-immer-immutability-the-easy-way-9d73d8f71cb3
[proposal-observable]: https://github.com/tc39/proposal-observable
[react-ducks-no-redux]: https://codesandbox.io/s/todo-app-without-redux-9yc57
[react-ducks-saga]: https://github.com/iamogbz/example-react-duck-saga
[react-redux]: https://react-redux.js.org/api/connect
[react-redux-tutorial]: https://react-redux.js.org/introduction/basic-tutorial
[redux-applymiddleware]: https://redux.js.org/api/applymiddleware#applymiddlewaremiddleware
[redux-toolkit]: https://redux-toolkit.js.org/api/createReducer