# redux-scope

> Reduce Redux boilerplate and improve modularity 🔭

Latest version **0.2.3** (published 2019-04-15) · MIT license · 0 weekly downloads

## Install

```sh
npm install redux-scope
pnpm add redux-scope
yarn add redux-scope
bun add redux-scope
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.2.3 |
| Published | 2019-04-15 |
| First published | 2019-02-28 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 2 |
| Unpacked size | 31.2 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 0 |
| Author | Ilijan Kotarac |
| Maintainers | ilijan |
| Keywords | redux scope, scoped redux, scoped actions, reduce redux boilerplate, redux helpers, redux actions, redux boilerplate, redux types, redux selectors, redux modules, modular redux |

## Links

- npm: https://www.npmjs.com/package/redux-scope
- Repository: https://github.com/ikotarac/redux-scope
- Homepage: https://github.com/ikotarac/redux-scope#readme
- Issues: https://github.com/ikotarac/redux-scope/issues
- npm.io page: https://npm.io/package/redux-scope

## Dependencies (2)

- [redux](https://npm.io/package/redux.md) ^4.0.1
- [lodash](https://npm.io/package/lodash.md) ^4.17.11

## Alternatives

- [lodash.assign](https://npm.io/package/lodash.assign.md) — 2.3M weekly downloads
- [lodash.chunk](https://npm.io/package/lodash.chunk.md) — 1.8M weekly downloads
- [react-native-ios-utilities](https://npm.io/package/react-native-ios-utilities.md) — 138.5K weekly downloads
- [@technically/lodash](https://npm.io/package/@technically/lodash.md) — 50.9K weekly downloads
- [@fluid-topics/ft-icon](https://npm.io/package/@fluid-topics/ft-icon.md) — 20.6K weekly downloads

## Recent versions

- 0.2.3 (latest) — 2019-04-15
- 0.2.2 — 2019-04-15
- 0.2.1 — 2019-04-14
- 0.2.0 — 2019-04-14
- 0.1.8 — 2019-03-11
- 0.1.7 — 2019-03-11
- 0.1.6 — 2019-03-11
- 0.1.5 — 2019-03-05
- 0.1.4 — 2019-03-05
- 0.1.3 — 2019-03-05
- 0.1.2 — 2019-03-04
- 0.1.1 — 2019-03-04
- 0.1.0 — 2019-03-04
- 0.0.0 — 2019-02-28

## README

# Redux Scope 🔭

Reduce Redux boilerplate and improve modularity.

Redux Scope logically brings together _actions_, _action types_, _reducers_ and _selectors_ by associating it with a _redux scope_.
It provides helpers for creating actions and selectors which take care of creating unique action types and keep selectors wired correctly as you refactor your modules.

![npm version](https://img.shields.io/npm/v/redux-scope.svg)
![npm downloads](https://img.shields.io/npm/dw/redux-scope.svg)

## Installation

`npm install redux-scope`

## Usage

Most of the apps are written as a collection of modules where each module has its own redux logic. Let's say we are writing a module taking care of user preferences, called `preferences`:

`src/modules/preferences/redux.js`

```javascript
import { createScope, createAction, createSelector } from 'redux-scope';

export const setFontSize = createAction('set-font-size');

function reducer(state = { fontSize: 'small' }, action) {
  switch (action.type) {
    case setFontSize.type:
      return {
        ...state,
        fontSize: action.payload,
      };
    default:
      return state;
  }
}

export const fontSize = createSelector(state => state.fontSize);

export const preferencesScope = createScope('preferences', reducer, {
  setFontSize,
  fontSizeSelector,
});
```

`src/app.js`

```javascript
import { createStore } from 'redux';
import thunk from 'redux-thunk';
import { createRootReducer } from 'redux-scope';
import {
  preferencesScope,
  setFontSize,
  fontSize as fontSizeSelector,
} from 'src/modules/preferences';

const rootReducer = createRootReducer({
  preferencesScope,
  // other scopes go here
});

const store = createStore(rootReducer, applyMiddleware(thunk));

// use redux as usual
store.dispatch(setFontSize('big'));
const fontSize = fontSizeSelector(store.getState()); // 'big'
```

## Redux Scope Lifecycle

Usually, Redux lifecycle can be separated into several phases:

1. all reducers are imported from their modules and combined together into a root reducer using `combineReducers`,
2. init action is fired to produce initial state,
3. store is ready for dispatching actions and reading from state.

But before that, in order for modules to cooperate well:

- action types are predefined and prefixed with module paths (or other unique identifier),
- every selector is defined to point to a correct place in resulting global state.

Redux Scope uses this separation between composing redux logic from different modules (Step 1) and actually using that logic (Step 3) to lazy evaluate action types and correct paths for selectors, so that you don't need to hardcode them inside modules.

Here is how Redux lifecycle looks with Redux Scope:

1. actions, selectors and reducer are added to a scope using `createScope`
2. all scopes are imported from their modules and combined together using `combineScopes`
3. root reducer is produced from scopes using `createRootReducer`, which also initializes all action types and selectors to correct values
4. init action is fired to produce initial state
5. store is ready for dispatching actions and reading from state

You don't have to think about action types, they will be prefixed automatically. When you write selectors with `createSelector` you only write how to select a property inside that module (`state => state.fontSize`), Redux Scope takes care of the rest.

## Creating Thunks

`src/modules/user/redux.js`

```javascript
import { createThunk, createScope } from 'redux-scope';
import { fetchUserDataAsync } from './my-user-api';

export const fetchUser = createThunk(fetchUserDataAsync, 'fetch-user');

const initialState = {
  data: null,
  loading: false,
  error: null,
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case fetchUser.type.request:
      return {
        ...state,
        loading: true,
        error: null,
      };
    case fetchUser.type.success:
      return {
        ...state,
        data: action.payload,
        loading: false,
        error: null,
      };
    case fetchUser.type.error:
      return {
        ...state,
        loading: false,
        error: action.error,
      };
    default:
      return state;
  }
}

export const userProfileScope = createScope('user', reducer, { fetchUser });
```

Let's say `fetchUserDataAsync` looks like this:

```javascript
function fetchUserDataAsync(userId, extraParam) {
  return UserApi.getUser(id, extraParam);
}
```

`fetchUser` thunk will dispatch 2 actions: `request`, and `success` or `error`, that will look like this:

```javascript
dispatch(fetchUser('id-123', true));

const requestAction = {
  type: 'user/fetch-user/request',
  request: ['id-123', true],
};

const successAction = {
  type: 'user/fetch-user/success',
  request: ['id-123', true],
  payload: {
    /* result of api call */
  },
};

const errorAction = {
  type: 'user/fetch-user/error',
  request: ['id-123', true],
  error: {
    /* caught error */
  },
};
```

## Composing Scopes

Redux scopes can be composed using `combineScopes`, analogous to how reducers can be composed using _combineReducers_.

We have already created two modules/scopes named `user` and `preferences`. Now, let's create a third module named `favorites` to illustrate how scope composition works.

`src/modules/favorites/redux.js`

```javascript
import { createScope, createAction, createSelector } from 'redux-scope';

export const addToFavorites = createAction('add-to-favorites');

function reducer(state = [], action) {
  switch(action.type) {
    case addToFavorites.type:
      return [...state, action.payload],
    default:
      return state;
  }
}

export const favorites = createSelector(); // by default you select state => state
export const favoritesScope = createScope('favorites', reducer, {
  addToFavorites,
  favorites,
});

```

Say we want to combine together `favorites` and `preferences` to form a new module named `user-stuff`:

`src/modules/user-stuff/redux.js`

```javascript
import { combineScopes } from 'redux-scope';
import { preferencesScope } from 'src/modules/preferences';
import { favoritesScope } from 'src/modules/favorites';

export userStuffScope = combineScopes('user-stuff', { preferencesScope, favoritesScope })
```

Now, at the top level:

`src/app.js`

```javascript
import { createStore } from 'redux';
import thunk from 'redux-thunk';
import { createRootReducer } from 'redux-scope';
import { userScope } from 'src/modules/user';
import { userStuffScope } from 'src/modules/user-stuff';

const rootReducer = createRootReducer({
  userScope,
  userStuffScope,
});

const store = createStore(rootReducer, applyMiddleware(thunk));
```

## Features

### Shape of the resulting state

✨ Generated root reducer from the last example produces state with the following shape:

```json
{
  "user": {
    "data": null,
    "error": null,
    "loading": false
  },
  "user-stuff": {
    "preferences": {
      "fontSize": "small"
    },
    "favorites": []
  }
}
```

### Action types are scoped automatically

Composing scopes automatically prefixes action types with scope names of all the parent scopes. Say, if we dispatch following actions:

```javascript
dispatch(addToFavorites({ userId: 'id-123' }));
dispatch(setFontSize('large'));
dispatch(fetchUser('id-123', true));
```

created actions would have automatically scoped action types:

```
'user-stuff/favorites/add-to-favorites'
```

```
'user-stuff/preferences/set-font-size'
```

```
'user/fetch-user/request'
'user/fetch-user/success'
'user/fetch-user/error'
```

### Selectors stay wired correctly

✨ All selectors are wired automatically, they recieve root state and work everywhere:

```javascript
loading(state); // false
fontSize(state); // 'small'
```

✨ No matter where you import and use your selectors, you always pass the root state, no need to pass the substate selector manually.

✨ The mechanism of nesting scopes using `composeScopes` makes sure all selectors remain connected to the right part of the state 🔬

✨ Your modules do not need to know where their reducer will be mounted, thus you get enhanced modularity.

✨ You can use your selectors like any other selector, compose them or use them with [reselect](https://github.com/reduxjs/reselect).

## Interop with plain Redux

### Connecting external reducer to Redux scope

```javascript
import { createScope } from 'redux-scope';
import { reducer } from 'src/modules/external-module';

export const externalScope = createScope('some-external-module', reducer);
```

### Exporting reducer to use with plain Redux

Provide a path to the mounting point so that selectors can work correctly:

`src/modules/module-that-uses-redux-scope/index.js`

```javascript
import { createRootReducer } from 'redux-scope';
import { userScope } from 'src/modules/user';
import { userStuffScope } from 'src/modules/user-stuff';

export const reducer = createRootReducer(
  {
    userScope,
    userStuffScope,
  },
  state => state.pathToThisModule,
);
```

Now you can use exported reducer like any other reducer, compose it with _combineReducers_, and all actions and selectors inside your module will work correctly.

## combineSelectors

`combineSelectors` is a convenience mapper that reduces boilerplate in writing `mapStateToProps`:

```javascipt
import { combineSelectors } from 'redux-scope';
import { fontSize, favorites } from './selectors';

// before
const mapStateToProps = (state, ownProps) => {
  fontSize: fontSize(state),
  favorites: favorites(state),
}

// after
const mapStateToProps = combineSelectors({
  fontSize,
  favorites
});
```

## I just want to use helpers without scope

No problem, `createAction`, `createThunk` and `createSelectors` accept one more parameter, that defines scope path or state selector, so you can use them without Redux Scope:

`just-helpers.js`

```javascript
import { createAction, createThunk, createSelectors } from 'redux-scope';
import { asyncCall } from './async-call';

const initialState = {
  someData: null,
  ids: [],
  count: 1,
};

const action = createAction('some-action', 'my/module');

const thunk = createThunk(asyncCall, 'my/module');

const { someData, ids, count } = createSelectors(
  initialState,
  state => state.my.module,
);
```

---
_Source: https://npm.io/package/redux-scope · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
