Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/adrienjt/redux-data-structures
Reducer factory functions for common data structures: counters, maps, lists (queues, stacks), sets, etc.
https://github.com/adrienjt/redux-data-structures
common-reducers counter data-structures dictionary higher-order-reducers map queue reducer-composition reducer-creation reducer-generator reducer-makers redux set stack toggle
Last synced: 2 months ago
JSON representation
Reducer factory functions for common data structures: counters, maps, lists (queues, stacks), sets, etc.
- Host: GitHub
- URL: https://github.com/adrienjt/redux-data-structures
- Owner: adrienjt
- License: mit
- Created: 2017-06-17T19:58:14.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2018-02-17T21:19:42.000Z (over 6 years ago)
- Last Synced: 2024-08-22T19:18:36.223Z (3 months ago)
- Topics: common-reducers, counter, data-structures, dictionary, higher-order-reducers, map, queue, reducer-composition, reducer-creation, reducer-generator, reducer-makers, redux, set, stack, toggle
- Language: JavaScript
- Homepage: https://redux-data-structures.js.org
- Size: 43.9 KB
- Stars: 158
- Watchers: 5
- Forks: 4
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Redux Data Structures
## Introduction
Redux Data Structures is a library of _reducer makers_.
Reducer makers help create common reducers like counters, maps, lists (queues, stacks), sets, etc. Most application states can be built by combining a handful of these standardized building blocks.
Redux Data Structures was developed for Redux, but does not depend on it. It can actually be used with any reactive state container, even a custom one; Redux Data Structures doesn't have any dependency.
## Getting Started
```
npm install --save redux-data-structures
```Here's an example from the [Redux README](https://github.com/reactjs/redux), rewritten with Redux Data Structures:
```javascript
import { createStore } from 'redux';
import { counter } from 'redux-data-structures';const myCounter = counter({
incrementActionTypes: ['INCREMENT'],
decrementActionTypes: ['DECREMENT'],
});const store = createStore(myCounter);
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
```## Configuring Data Structures
Here's a more advanced example--with the same reducer maker--of a counter from 10 to 0, decreasing as a function of the action payload, then reset, representing life points for example:
```javascript
import { createStore } from 'redux';
import { counter } from 'redux-data-structures';const lifePoints = counter({
initialState: 10,
decrementActionTypes: ['PUNCH', 'KICK'],
decrement: action => action.value,
min: () => 0, // action => number
resetActionTypes: ['INSERT_COIN'],
});const store = createStore(lifePoints);
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'PUNCH', value: 5 });
// 5
store.dispatch({ type: 'KICK', value: 7 });
// 0
store.dispatch({ type: 'INSERT_COIN' });
// 10
```## Combining Data Structures
Let's build a classic todo app with Redux Data Structures:
```javascript
import { createStore, combineReducers } from 'redux';
import { map, set, value } from 'redux-data-structures';const todos = map({
addActionTypes: ['ADD_TODO'],
removeActionTypes: ['REMOVE_TODO'],
});const completedTodos = set({
toggleActionTypes: ['TOGGLE_TODO'],
removeActionTypes: ['REMOVE_TODO'],
keyGetter: action => action.payload.id,
});const visibilityFilter = value({
initialState: 'SHOW_ALL',
setActionTypes: ['SET_VISIBILITY_FILTER'],
valueGetter: action => action.payload.filter,
});const rootReducer = combineReducers({
todos,
completedTodos,
visibilityFilter,
});const store = createStore(rootReducer);
```That's all for the store! We've relied heavily on the reducer makers' default options, which presume that:
1. actions adhere to the [Flux Standard Action](https://github.com/acdlite/flux-standard-action) (actions are plain Javascript object with a `type` and `payload` properties),
1. and Todos are identified by an `id` property, used as a key in the `todos` map (and the `completetedTodos` set).Now let's subscribe to the store and dispatch a few actions:
```javascript
store.subscribe(() => { console.log(JSON.stringify(store.getState(), null, 2)); });store.dispatch({
type: 'ADD_TODO',
payload: {
id: 0,
text: 'Go fishing',
},
});
// {
// "todos": {
// "byId": {
// "0": {
// "id": 0,
// "text": "Go fishing"
// }
// },
// "allIds": [
// 0
// ]
// },
// "completedTodos": {},
// "visibilityFilter": "SHOW_ALL"
// }
```Notice that `todos` is [normalized, for the reasons explained in the Redux documentation](http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html).
```javascript
store.dispatch({
type: 'TOGGLE_TODO',
payload: { id: 0 },
});
// {
// "todos": {
// "byId": {
// "0": {
// "id": 0,
// "text": "Go fishing"
// }
// },
// "allIds": [
// 0
// ]
// },
// "completedTodos": {
// "0": true
// },
// "visibilityFilter": "SHOW_ALL"
// }
```Compared to the original Redux Todo example, we've separated the Todo items (id, text) from their completion state. If needed, they could be combined with a [selector](http://redux.js.org/docs/recipes/ComputingDerivedData.html).
```javascript
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
payload: { filter: 'SHOW_COMPLETED' },
});
// {
// "todos": {
// "byId": {
// "0": {
// "id": 0,
// "text": "Go fishing"
// }
// },
// "allIds": [
// 0
// ]
// },
// "completedTodos": {
// "0": true
// },
// "visibilityFilter": "SHOW_COMPLETED"
// }
store.dispatch({
type: 'REMOVE_TODO',
payload: { id: 0 },
});
// {
// "todos": {
// "byId": {},
// "allIds": []
// },
// "completedTodos": {},
// "visibilityFilter": "SHOW_COMPLETED"
// }
```The `REMOVE_TODO` action is reduced both by the `todos` map and the `completedTodos` set.
## Data Structures
So far, the following data structures have been implemented (corresponding action types are indicated in parentheses):
- Boolean (set to true, set to false, toggle)
- Counter (increment, decrement)
- List (queue or stack: enqueue, dequeue, push, pop)
- Map (add, remove, change)
- Set (add, remove, toggle)
- Value (set)All data structures can be reset to their initial state, and, if applicable (for lists, maps, and sets), emptied.
## API
Each reducer maker is a higher-order function of a single `options` object and returns a reducer:
```javascript
{ ...options } => (state, action) => state
```For each reducer maker, we describe below how the `options` object is destructured, its default property values, and how some specific properties are used.
Defaults can--and in a lot of cases __should__--be overridden.
Each category of actions, e.g., `decrementActionTypes`, is an array of action types (i.e., strings), so that several action types can have the same result (cf. [Configuring Data Structures, above](#configuring-data-structures), where both `PUNCH` and `KICK` decrement `lifePoints`).
### Boolean
```javascript
{
initialState = false,
trueActionTypes = [],
additionalConditionToTrue = () => true,
falseActionTypes = [],
additionalConditionToFalse = () => true,
toggleActionTypes = [],
resetActionTypes = [],
}
````additionalConditionToTrue` and `additionalConditionToFalse` are functions of `action` and are used as such:
```javascript
// ...
if (trueActionTypes.includes(action.type) && additionalConditionToTrue(action)) {
return true;
} else if (falseActionTypes.includes(action.type) && additionalConditionToFalse(action)) {
return false;
}
// ...
```The default `() => true` is equivalent to no additional condition.
### Counter
```javascript
{
initialState = 0,
incrementActionTypes = [],
increment = () => 1,
max,
decrementActionTypes = [],
decrement = () => 1,
min,
resetActionTypes = [],
}
````increment`, `decrement`, `max`, and `min` are functions of `action`. If `max` is `undefined`, it is not enforced. Same for `min`.
### List
```javascript
{
initialState = [],
enqueueActionTypes = [],
dequeueActionTypes = [],
pushActionTypes = [],
popActionTypes = [],
itemGetter = action => action.payload,
resetActionTypes = [],
emptyActionTypes = [],
}
```A list can be used as a queue or stack. `enqueueActionTypes` and `pushActionTypes` add items to the list, using the `itemGetter`. The default `itemGetter` adds the [Flux Standard Action](https://github.com/acdlite/flux-standard-action) `payload` to the list.
### Map
```javascript
{
initialState = {
byId: {},
allIds: [],
},
addActionTypes = [],
changeActionTypes = [],
removeActionTypes = [],
keyGetter = action => action.payload.id,
itemGetter = action => ({...action.payload}),
itemModifier = (item, action) => ({...item, ...action.payload}),
resetActionTypes = [],
emptyActionTypes = [],
}
````map` uses the [normalized state shape recommended by Redux](http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html), as can be seen from the default `initialState`. Warning: if you overwrite `initialState`, use the same format!
The default `keyGetter` assumes that the action payload has an `id` property. The default `itemModifier` overwrites the item's properties (but does not delete the ones that have disappeared in the new action payload).
### Set
```javascript
{
initialState = {},
addActionTypes = [],
removeActionTypes = [],
toggleActionTypes = [],
keyGetter = action => action.payload,
resetActionTypes = [],
emptyActionTypes = [],
}
```In Redux Data Structures, a set's state is a plain Javascript object with boolean properties, i.e. if and only if `key` is in the set, `key` is a property of `state` whose value is `true`. Example:
```javascript
{ key: true }
```
When a key is removed from the set, the corresponding property is deleted from the state object:
```javascript
{}
```### Value
```javascript
{
initialState = null,
setActionTypes = [],
valueGetter = action => action.payload,
resetActionTypes = [],
}
````value` is the simplest data structure (to the extent that calling it a data structure is arguable).
## Performance
Redux Data Structures doesn't focus on performance, but on developer productivity. In most cases, performance won't be an issue. If it is, please write an issue or submit a pull request.
## Contributing
The code is written in modern Javascript, transpiled with Babel, using Jest for tests. Pull requests are welcome.
## License
[MIT](LICENSE.md)
## Author
Adrien Trouillaud, [Codology.net](https://codology.net)