UI State management overview

Some Stripes modules including stripes-core and ui-eholdings are written using redux, react-redux and redux-observable for state management. stripes-core is a wrapper for other UI modules and their reducers and epic is a root one. Every UI application is able to register its own reducers and epics in root. This is done by using withRoot HOC from '@folio/stripes-core/src/components/Root/RootContext' which provides the addReducer and addEpic methods. The main idea is to call these methods by passing all combined reducers and epics of the particular module and not one by one.

import React from 'react';
import PropTypes from 'prop-types';

import { withRoot } from '@folio/stripes-core/src/components/Root/RootContext';

import { STATE_MANAGEMENT } from './utils/constants';

import { reducer, epic } from './redux';

class DataImport extends React.Component {
  static propTypes = {
    root: PropTypes.shape({
      addReducer: PropTypes.func.isRequired,
    }).isRequired,
  };

  constructor(props, context) {
    super(props, context);

    // add epic and reducer to the application store
    props.root.addReducer(STATE_MANAGEMENT.REDUCER, reducer);
    props.root.addEpic(STATE_MANAGEMENT.EPIC, epic);
  }
  
  ...
}

export default withRoot(DataImport);

STATE_MANAGEMENT.REDUCER and STATE_MANAGEMENT.EPIC are constants which usually equal to the name of the particular project (e.g. data-import).

Redux

Redux is a global state management object for your react app. The basic idea comes from the flux architecture. redux is needed because react app gets bigger. It’s hard to manage state. If two child component share the same state from parents. The child must pass the state back to parent and then to the other child component. To solve this problem, we need a global store.

Not all state should be placed inside redux. For example whether a dropdown is open is okay to store in component state. Additionally redux can be replaced with usage of react context feature.

An example of action type:

export const ADD_RECIPE = 'ADD_RECIPE';


And example of action:

export const addRecipe = payload => ({
 type: types.ADD_RECIPE,
 payload,
})


An example of reducer:

const initialState = {
  recipes: [],
};


export default function(state = initialState, action){
  switch (action.type) {
    case ADD_RECIPE:
      return [
        ...state,
        recipes: {
          ...state.recipes,
          {
            name: action.recipe.name,
            ingredients: action.recipe.ingredients,
          },
        },
      ];
    default:
      return state;
  }
}


To incorporate redux to the app 

// reducers/index.js
const rootReducer = combineReducers({
  recipes,
});

export default rootReducer;


// index.js
const store = createStore(reducer);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

In order to get access to the store the connect function is used from react-redux library. The full example can be found here.

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps,
)(TodoList);

export default VisibleTodoList;

Redux-observable

redux-observable is a middleware in Redux. Middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more. redux-observable is RxJS 6-based middleware for Redux. Compose and cancel async actions to create side effects and more. Epic is a term in redux-observable which denotes an asynchronous action which are usually called side effects. An Epic is the core primitive of redux-observable. It is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.


function (action$: Observable<Action>, state$: StateObservable<State>): Observable<Action>;


redux-observable is based on RxJs which is Reactive Extensions Library for JavaScript. RxJs is a set of libraries for composing asynchronous and event-based programs using observable sequences and fluent query operators that many of you already know by Array#extras in JavaScript. Using RxJS, developers represent asynchronous data streams with Observables, query asynchronous data streams using our many operators, and parameterize the concurrency in the asynchronous data streams using Schedulers. Simply put, RxJS = Observables + Operators + Schedulers.


Example of sample epic:

import { of as of$ } from 'rxjs/observable/of';
import { concat as concat$ } from 'rxjs/observable/concat';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';

import { SEARCH, searchInProgress, searchSuccess, searchFailed } from '../../actions';

export const searchEpic = action$ => {
  return action$
    .ofType(SEARCH)
    .switchMap(() =>
      concat$(
        of$(searchInProgress()),
        of$([{ a: 1 }, { a: 2 }])
          .delay(1000)
          .map(data => searchSuccess(data))
          .catch(e => of$(searchFailed(e)))
      )
    );
};


Structure

A good place to store redux stuff is under src/redux folder. The actions folder stands for react actions, redux for reducers, epics for side effects.

Debugging

The Redux team implemented a handy tool for working with state management. It is redux-devtools chrome extension. It provides a way to see what exact actions with what payload was fired and allows you to see the whole application state and much more. Folio UI application has the needed setup so the only requirement is to install extension.

Useful resources

  1. https://egghead.io/courses/getting-started-with-redux
  2. https://egghead.io/courses/building-react-applications-with-idiomatic-redux
  3. https://redux-observable.js.org/
  4. https://github.com/ReactiveX/rxjs