An open API service indexing awesome lists of open source software.

https://github.com/kevinast/feature-redux

feature-u redux integration
https://github.com/kevinast/feature-redux

feature feature-u features react redux

Last synced: 9 months ago
JSON representation

feature-u redux integration

Awesome Lists containing this project

README

          

# feature-redux *(feature-u redux integration)*

**feature-redux** is your [feature-u] integration point to [redux]!
It promotes the [`reducerAspect`] _(a [feature-u] plugin)_ that
facilitates [redux] integration to your features.

**Backdrop:**

    [feature-u] is a utility that facilitates feature-based project
    organization for your [react] project. It helps organize your
    project by individual features. [feature-u] is extendable. It
    operates under an open plugin architecture where Aspects integrate
    **feature-u** to other framework/utilities that match your specific
    run-time stack.


[![Build Status](https://travis-ci.org/KevinAst/feature-redux.svg?branch=master)](https://travis-ci.org/KevinAst/feature-redux)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/9c93a52aa6b6484ebd382ac976176836)](https://www.codacy.com/app/KevinAst/feature-redux?utm_source=github.com&utm_medium=referral&utm_content=KevinAst/feature-redux&utm_campaign=Badge_Grade)
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/9c93a52aa6b6484ebd382ac976176836)](https://www.codacy.com/app/KevinAst/feature-redux?utm_source=github.com&utm_medium=referral&utm_content=KevinAst/feature-redux&utm_campaign=Badge_Coverage)
[![Known Vulnerabilities](https://snyk.io/test/github/kevinast/feature-redux/badge.svg?targetFile=package.json)](https://snyk.io/test/github/kevinast/feature-redux?targetFile=package.json)
[![NPM Version Badge](https://img.shields.io/npm/v/feature-redux.svg)](https://www.npmjs.com/package/feature-redux)

**Overview:**

    **feature-redux** configures [redux] through the [`reducerAspect`]
    (_which is supplied to_ **feature-u**'s [`launchApp()`]). This
    extends **feature-u**'s [`Feature`] object by adding support for the
    `Feature.reducer` property, referencing feature-based [reducers].

    A feature-based reducer is simply a normal reducer that manages the
    feature's slice of the the overall appState. The only thing different
    is it must be embellished with [`slicedReducer()`], which provides
    instructions on where to insert it in the overall top-level appState.

    Only [reducers] are of interest to **feature-redux** because that is
    what is needed to configure [redux]. All other **redux** items (_[actions],
    [selectors], etc._) are internal implementation details of the feature.

    It is important to understand that you continue to use [redux] the
    same way you always have. It's just that now you are dealing with a
    **smaller context** ... _within the boundaries of your feature_!

    As an aside, because **feature-redux** manages redux, it also
    provides an integration point to other Aspects that need to inject
    their [redux middleware].

Let's see how this all works together ...

## At a Glance

- [Install](#install)
- [Usage](#usage)
- [A Closer Look](#a-closer-look)
- [Actions](#actions)
- [Public Actions](#public-actions)
- [Action Uniqueness (Single Source of Truth)](#action-uniqueness-single-source-of-truth)
- [Reducers (state)](#reducers-state)
- [Sliced Reducers]
- [State Root (Single Source of Truth)](#state-root-single-source-of-truth)
- [Selectors (encapsulating state)](#selectors-encapsulating-state)
- [Public Selectors](#public-selectors)
- [Feature State Location (Single Source of Truth)](#feature-state-location-single-source-of-truth)
- [Interface Points](#interface-points)
- [Inputs](#inputs)
- [Exposure](#exposure)
- [Error Conditions](#error-conditions)
- [API](#api)
- [`reducerAspect: Aspect`](#reduceraspect-aspect)
- [`slicedReducer(slice, reducer): reducerFn`](#slicedreducer)
- [Potential Need for Polyfills](#potential-need-for-polyfills)

## Install

- **peerDependencies** ... you should already have these, **because
this is our integration point** _(but just in case)_:

```shell
npm install --save feature-u
npm install --save react
npm install --save redux
npm install --save react-redux
```

- **the main event**:

```shell
npm install --save feature-redux
```

**SideBar**: Depending on how current your target browser is
_(i.e. it's JavaScript engine)_, you may need to polyfill your app
_(please refer to [Potential Need for
Polyfills](#potential-need-for-polyfills))_.

## Usage

1. Within your mainline, register the **feature-redux**
[`reducerAspect`] to **feature-u**'s [`launchApp()`] _(see: `**1**`
below)_.

**src/app.js**
```js
import {launchApp} from 'feature-u';
import {createReducerAspect} from 'feature-redux'; // **1**
import features from './feature';

export default launchApp({

aspects: [
createReducerAspect(), // **1**
... other Aspects here
],

features,

registerRootAppElm(rootAppElm) {
ReactDOM.render(rootAppElm,
getElementById('myAppRoot'));
}
});
```

2. Within each feature that maintains state, simply register the
feature's reducer through the `Feature.reducer` property _(using
**feature-u**'s [`createFeature()`])_ ... _see: `**2**` below_.

Because the state of each feature is combined into one overall
appState, the feature reducer must identify it's root location,
through the [`slicedReducer()`] function. This **slice** can
optionally reference a federated namespace corresponding to the
desired target shape.

**src/feature/myXyzFeature/index.js**
```js
import {createFeature} from 'feature-u';
import {slicedReducer} from 'feature-redux';
import myXyzFeatureReducer from './state';

export default createFeature({

name: 'myXyzFeature',

reducer: slicedReducer('my.feature.state.location', myXyzFeatureReducer), // **2**

... snip snip (other aspect properties here)
});
```

**Well that was easy!!** At this point **redux** is **completely setup
for your application!** The [redux store] has been created and is
accessible through the standard **redux** [`connect()`] HoC,
or the newer [`Hooks`] API.

In the nutshell, that's a highlight of most everything you need to know to use
**feature-redux**! _Go forth and compute!_

## A Closer Look

**feature-redux** automatically configures [redux] by creating the
[store] _(accumulating feature [reducers] across your app)_, and
making the [store] available through the standard **redux**
[`connect()`] HoC (or the newer [`Hooks`] API), _(by injecting the
standard **redux** [``] component at the root of your app)_.

It is important to understand that your interface to [redux] has not
changed in any way. In other words, you continue to use [redux] the
same way you always have. It's just that now you are dealing with a
**smaller context** ... _within the boundaries of your feature_!

With that said, as always, you should strive to keep each feature
isolated, so it is truly **plug-and-play**.
Working with [redux], involves interacting with [actions],
[reducers], and [selectors]. Let's take a closer look at some
**Feature-Based Best Practices** in regard to [redux] usage.

### Actions

Within the [redux] framework, [actions] are the basic building blocks
that facilitate application activity.

- Actions follow a pre-defined convention that promote an action type
and a type-specific payload.

- Actions are dispatched throughout the system (both UI components,
and logic modules).

- Actions are monitored by [reducers] (which in turn change state), and
trigger UI changes.

- Actions can also be monitored by logic modules _(when using
[redux-logic])_, that implement a variety of app-level logic
... things like asynchronously gathering server resources, and even
dispatching other actions.

In general, **[actions] are considered to be an internal detail of the
feature**, and therefore are **NOT managed by feature-u**. In other
words, *each feature will define and use it's own set of [actions]*.

This allows you to manage your actions however you wish. Best
practices prescribe that actions be created by [action creators]
_(functions that return actions)_. It is common to manage your action
creators and action types through a library like [action-u] or
[redux-actions].

#### Public Actions

    There are a small number of cases where feature-based actions may need
    to be promoted outside of a feature's boundary. Say, for example,
    featureA's reducer needs to monitor one of featureB's actions, or one
    of featureB's logic modules needs to dispatch a featureA action.

    When this happens the **feature-u** [`fassets aspect`] can be used for
    this promotion.

    Please note that in consideration of feature encapsulation, *best
    practices would strive to minimize the public promotion of actions
    outside the feature boundary*.

#### Action Uniqueness (Single Source of Truth)

    One characteristic that [actions] must adhere to is: **action types must
    be unique across the entire app**, *because they are interpreted at an
    app-level scope*.

    This uniqueness is the responsibility of your implementation, because
    [feature-u] is not involved. This may simply naturally happen in
    your implementation.

    If however, you wish to systematically insure this uniqueness, the
    simplest thing to do is to **prefix all your action types with the
    feature name** _(**feature-u** guarantees all feature names are unique)_.
    This has the *added benefit of readily associating dispatched action
    flows to the feature they belong to*.

    **Note**: Ideally you could use the `Feature.name` as the
    single-source-of-truth, however importing feature from your action
    modules is problematic _(the [`Feature`] object will most likely not be
    fully resolved during in-line code expansion)_. As a result, a **best
    practice** is to import a separate `featureName` constant (*that
    simply holds the name*). Here is an example:

    **src/feature/timer/featureName.js**
    ```js
    export default 'timer';
    ```

    **src/feature/timer/actions.js**
    ```js
    import featureName from './featureName';

    // action type constants
    export const TIMER_START = `${featureName}.TIMER_START`;
    export const TIMER_CANCEL = `${featureName}.TIMER_CANCEL`;
    export const TIMER_RESET = `${featureName}.TIMER_RESET`;
    export const TIMER_END = `${featureName}.TIMER_END`;

    ... snip snip
    ```

### Reducers (state)

Within the [redux] framework, [reducers] monitor [actions], changing
appState, which in turn triggers UI changes.

Each feature (that maintains state), defines it's own reducer,
maintaining it's slice of the overall appState.

While these [reducers] are truly opaque assets _(internal to the
feature)_, they are of interest to **feature-redux** to the extent
that they are needed to configure redux.

Each feature that maintains state, simply registers it's reducer
through the `Feature.reducer` property _(using **feature-u**'s
[`createFeature()`])_.

Because [reducers] may require access to **feature-u**'s [`Fassets
object`] during code expansion, this property can also be a
**feature-u** [`expandWithFassets()`] callback _(a function that
returns the reducer)_ ... please refer to **feature-u**'s discussion
of [Managed Code Expansion].

#### Sliced Reducers

    Because **feature-redux** must combine the [reducers] from all features
    into one overall appState, it requires that each reducer be
    embellished through the [`slicedReducer()`] function.

    This merely injects a slice property on the reducer function
    _(interpreted by **feature-redux**)_, specifying the location of the
    reducer within the top-level appState tree.

    This **slice** can optionally reference a federated namespace
    corresponding to the desired target shape.

    As an example, consider the following definition _(see: `**4**`)_:

    ```js
    const currentView = createFeature({
    name: 'currentView',
    reducer: slicedReducer('view.currentView', currentViewReducer), // **4**
    ...
    });

    const fooBar = createFeature({
    name: 'fooBar',
    reducer: slicedReducer('view.fooBar', fooBarReducer), // **4**
    ...
    });
    ```

    Which yields the following overall appState:

    ```js
    appState: {
    view: {
    currentView {
    ... state managed by currentViewReducer
    },
    fooBar: {
    ... state managed by fooBarReducer
    },
    },
    }
    ```

#### State Root (Single Source of Truth)

    You are free to use any root location for your feature's state
    (i.e. the slice). In many cases, this is dictated by the overall
    state shape.

    However, depending on the context, it is often useful to use the
    `featureName` somewhere within this definition. This **best
    practice** has the added benefit of readily associating each slice of
    state to the feature they belong to.

    We can refine the example above, by programmatically injecting the
    `featureName`. This yields the same result as before, but benefits
    from the **single-source-of-truth** principle.

    ```js
    import featureName from './featureName';
    import reducer from './reducer';

    const currentView = createFeature({
    name: featureName,
    reducer: slicedReducer(`view.${featureName}`, reducer),
    ...
    });
    ```

### Selectors (encapsulating state)

[Selectors] are a [redux] best practice which encapsulates both the
state shape location and the business logic interpretation of that
state.

Selectors should be used to encapsulate all your state. Most
selectors are promoted/used internally within the feature _(defined in
close proximity to your [reducers])_.

#### Public Selectors

    While **feature-redux** does not directly manage anything about
    selectors, a feature may wish to promote some of it's selectors using
    the **feature-u** [`fassets aspect`].

    Please note that in consideration of feature encapsulation, *best
    practices would strive to minimize the public promotion of feature
    state (and selectors) outside the feature boundary*.

#### Feature State Location (Single Source of Truth)

    Another benefit of [`slicedReducer()`] is that not only does it
    embellish the reducer with a `slice` property _(interpreted by
    **feature-redux**)_, it also injects a selector that returns the
    slicedState root, given the appState:

    ```js
    reducer.getSlicedState(appState): slicedState
    ```

    In our case the slicedState is one in the same as the featureState, so
    as a **best practice** it can be used in all your selectors to further
    encapsulate this detail (**employing another single-source-of-truth
    principle**).

    Here is an example _(see: `**5**` and `**6**`)_:

    ```js
    // **5** DEFINITION
    /** Our feature state root (a single-source-of-truth) */
    const getFeatureState = (appState) => reducer.getSlicedState(appState);

    // **6** USAGE
    /** Is device ready to run app */
    export const isDeviceReady = (appState) => getFeatureState(appState).status === 'READY';

    ... more selectors
    ```

## Interface Points

The primary accomplishment of **feature-redux** is the creation (_and
configuration_) of the [redux store]. The **Aspect Interface** to
this process (_i.e. the inputs and outputs_) are documented here.

### Inputs

1. **Primary Input**:

The primary input to **feature-redux** is the set of [reducers]
that make up the overall app reducer. This is specified by each of
your features (_that maintain state_) through the `Feature.reducer`
property, containing a [`slicedReducer()`] that
manages the state of that corresponding feature.

2. **Middleware Integration**:

Because **feature-redux** manages [redux], other Aspects can
promote their [redux middleware] through **feature-redux**'s
`Aspect.getReduxMiddleware()` API (an "aspect cross-communication
mechanism"). As an example, the [feature-redux-logic] Aspect
integrates **redux-logic**.

3. **Enhancer Integration**:

Because **feature-redux** manages [redux], other Aspects can
promote their [redux enhancer] through **feature-redux**'s
`Aspect.getReduxEnhancer()` API (an "aspect cross-communication
mechanism").

### Exposure

1. **Primary Output**:

The primary way in which **feature-redux** exposes [redux] to your
app is by injecting the standard **redux** [``] component at
the root of your application DOM. This enables app component
access to the [redux store] (along with it's `dispatch()` and
`getState()`) through the standard **redux** [`connect()`] HoC,
or the newer [`Hooks`] API.

2. **Application Life Cycle Hooks - namedParams**:

**feature-redux** promotes the redux `getState`/`dispatch`
functions as namedParams of **feature-u**'s [Application Life Cycle
Hooks].

3. **Redux Middleware**:

Because **feature-redux** allows other aspects to inject their
[redux middleware], whatever that middleware exposes is made
available. As an example, the [feature-redux-logic] Aspect
injects **redux-logic**.

4. **Redux Enhancer**:

Because **feature-redux** allows other aspects to inject their
[redux enhancer], whatever that enhancer exposes is made
available.

5. **Other**:

- For good measure, **feature-redux** promotes the [redux store]
through the `Aspect.getReduxStore()` method. This provides direct
access to the [store] to any external process that needs it.

- Integration with Redux DevTools is automatically configured (when
detected).

### Error Conditions

- **NO REDUCERS**:

When **feature-redux** detects that no reducers have been
specified by any of your features, it will (by default) throw the
following exception:

```
***ERROR*** feature-redux found NO reducers within your features
... did you forget to register Feature.reducer aspects in your features?
(please refer to the feature-redux docs to see how to override this behavior).
```

Most likely this should in fact be considered an error _(for example
you neglected to specify the reducer aspects within your features)_.
**The reasoning is**: _why would you not specify any reducers if your
using redux?_

You can change this behavior through the `allowNoReducers` parameter:

```js
createReducerAspect({allowNoReducers: true})
```

- when `true`, and no reducers are found, redux will be configured
with an identity reducer (accompanied with a WARNING logging
probe).

- you can also specify your own app-wide reducer function in place
of the `true` value, which will be used ONLY in the scenario where
no reducers were specified by your features.

## API

### reducerAspect: Aspect

    ```
    API: createReducerAspect({name:'reducer',
    initialState:undefined,
    allowNoReducers:false}): reducerAspect
    ```

    The `reducerAspect` is the [feature-u] plugin that facilitates
    [redux] integration to your features.

    **PARAMS**: _(**Please Note**: only named parameters are used)_

    - **name**: The name of this reducer (defaults to 'reducer')

    - **initialState**: An optional pre-loaded state that will become the initial
    state of your store.

    Typically this is not needed, because your reducers will define the
    initial state.
    However, it can be useful to hydrate the state from the server (in
    universal apps), or to restore a previously serialized user session.

    When supplied, any validation/errors will be detected by redux, as
    it is passed directly to it's `createStore()`.

    - **allowNoReducers**: A boolean -or- app-wide reducer function, that
    specifies what to do when **no reducers** are found in any or your
    features (defaults to `false` which throws an error) ... see: **NO
    REDUCERS** section of [Error Conditions](#error-conditions)

    **USAGE**:

    - Within your mainline, register the **feature-redux**
    `reducerAspect` to **feature-u**'s [`launchApp()`].

    - Within each feature that maintains state, simply register the
    feature's reducer through the `Feature.reducer` property _(using
    **feature-u**'s [`createFeature()`])_.

    Because the state of each feature is combined into one overall
    appState, the feature reducer must identify it's root location,
    through the [`slicedReducer()`] function. This **slice** can
    optionally reference a federated namespace corresponding to the
    desired target shape.

    Please refer to the [Usage] section for examples of this process.

### slicedReducer()

    **API:** `slicedReducer(slice, reducer): reducer`

    Embellish the supplied reducer with a slice property - a
    specification (interpreted by **feature-redux**) as to the
    location of the reducer within the top-level appState tree.

    Please refer to the [Sliced Reducers] section for a
    complete description with examples.

    **Note:** `slicedReducer()` should always wrap the the outer
    function passed to [`createFeature()`], even when [`expandWithFassets()`]
    is used. This gives your app code access to the embellished
    `getSlicedState()` selector, even prior to expansion occurring (_used
    as a single-source-of-truth in your selector definitions_).

    **Parameters**:

    - **slice**: string

    The location of the managed state within the overall top-level
    appState tree. This can be a federated namespace _(delimited by
    dots)_. Example: `'views.currentView'`

    - **reducer**: reducerFn

    The redux [reducer] function to be embellished with the slice
    specification.

    **Return**: reducerFn

      the supplied reducer, embellished with both the slice and a
      convenience selector:

      ```js
      reducer.slice: slice
      reducer.getSlicedState(appState): slicedState
      ```

## Potential Need for Polyfills

The implementation of this library employs modern es2015+ JavaScript
constructs. Even though the library distribution is transpiled to
[es5](https://en.wikipedia.org/wiki/ECMAScript#5th_Edition) _(the
least common denominator)_, **polyfills may be required** if you are
using an antiquated JavaScript engine _(such as the IE browser)_.

We take the approach that **polyfills are the responsibility of the
client app**. This is a legitimate approach, as specified by the [W3C
Polyfill Findings](https://www.w3.org/2001/tag/doc/polyfills/)
_(specifically [Advice for library
authors](https://www.w3.org/2001/tag/doc/polyfills/#advice-for-library-and-framework-authors))_.

- polyfills should only be introduced one time _(during code expansion
of the app)_
- a library should not pollute the global name space _(by including
polyfills at the library level)_
- a library should not needlessly increase it's bundle size _(by
including polyfills that are unneeded in a majority of target
environments)_

As it turns out, **app-level polyfills are not hard to implement**,
with the advent of third-party utilities, such as babel:

- simply import [babel-polyfill](https://babeljs.io/docs/en/babel-polyfill.html)
- or use babel's
[babel-preset-env](https://babeljs.io/docs/en/babel-preset-env.html)
in conjunction with babel 7's `"useBuiltins": "usage"` option

**If your target JavaScript engine is inadequate, it will generate
native run-time errors, and you will need to address the polyfills.**
Unfortunately, in many cases these errors can be very obscure _(even
to seasoned developers)_. The following [Babel Feature
Request](https://github.com/babel/babel/issues/8089) _(if/when
implemented)_ is intended to address this issue.


[`reducerAspect`]: #reduceraspect-aspect
[Sliced Reducers]: #sliced-reducers
[Usage]: #usage
[`slicedReducer()`]: #slicedreducer


[feature-redux-logic]: https://github.com/KevinAst/feature-redux-logic/


[feature-u]: https://feature-u.js.org/
[`launchApp()`]: https://feature-u.js.org/cur/api.html#launchApp
[`createFeature()`]: https://feature-u.js.org/cur/api.html#createFeature
[`expandWithFassets()`]: https://feature-u.js.org/cur/api.html#expandWithFassets
[`fassets aspect`]: https://feature-u.js.org/cur/api.html#fassets
[`Feature`]: https://feature-u.js.org/cur/api.html#Feature
[`Fassets object`]: https://feature-u.js.org/cur/api.html#Fassets
[Managed Code Expansion]: https://feature-u.js.org/cur/crossCommunication.html#managed-code-expansion


[react]: https://reactjs.org/

[redux]: https://redux.js.org/
[redux store]: https://redux.js.org/glossary#store
[store]: https://redux.js.org/glossary#store
[redux middleware]: https://redux.js.org/glossary#middleware
[redux enhancer]: https://redux.js.org/glossary#store-enhancer
[actions]: https://redux.js.org/glossary#action
[action creators]: https://redux.js.org/glossary#action-creator
[reducers]: https://redux.js.org/glossary#reducer
[reducer]: https://redux.js.org/glossary#reducer
[selectors]: https://gist.github.com/abhiaiyer91/aaf6e325cf7fc5fd5ebc70192a1fa170
[`connect()`]: https://react-redux.js.org/api/connect
[`Hooks`]: https://react-redux.js.org/api/hooks
[``]: https://react-redux.js.org/api/provider


[redux-logic]: https://github.com/jeffbski/redux-logic


[action-u]: https://action-u.js.org/
[redux-actions]: https://github.com/reduxactions/redux-actions
[Application Life Cycle Hooks]: https://feature-u.js.org/cur/appLifeCycle.html