# 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),
2. 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](/my-docs/redux/tools-1/redux-data-structures/license.md)

## Author

Adrien Trouillaud, [Codology.net](https://codology.net)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bryan-guner.gitbook.io/my-docs/redux/tools-2/redux-data-structures.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
