EFX
An effectful state management system for React applications, designed for simplicity, testability and type-safety. Inspired by redux and reader monads.
Table of Contents
- Installation
- Centralized State
- Actions
- Connecting React Components to the Store
- Testing
- Why not Redux?
Installation
yarn add efx
Centralized State
To achieve full type coverage, EFX requires a single file of boilerplate, conventionally named efx.js
. This file can be more or less copy-pasted with the following contents:
// ./efx.js // @flow; ; const Store: Class<EFX.Store<AppState>> = EFXStore; const Provider: Class<EFX.Provider<AppState>> = EFXProvider; ; const makeAction: <A B>Action<A B> => Action<A B> = EFXmakeAction; type DefaultProps = {}; ; : React.ComponentType<OP> { const defaultProps = {}; return EFX;}
Actions
An Action is an effectful function.
It is the single unit of abstraction in EFX; no reducers, no action creators, no constants.
Synchronous actions:
To create an action that modifies some state:
// ./AppActions.js // @flow; const addToFoo: Action<number number> = ;
To dispatch an action, e.g. in the body of some other action:
const addTenToFooAndLog: Action<void void> = ;
Asynchronous actions:
To create an async action that modifies some state:
// ./AppActions.js // @flow; const addToFooAsync: Action<number Promise<number>> = ;
To dispatch an async action, e.g. in the body of some other async action:
const addTenToFooAndLogAsync: Action<void Promise<void>> = ;
Connecting React Components to the Store
The React component below does the following:
- Renders text derived from the value of
store.state.foo
- On click of a button, modifies the value of
store.state.foo
// ./MyComponent.js // @flow;; type OwnProps = {}; type ContainerProps = foo: number; const mapStateToProps = foo ; Component<Connect<OwnProps ContainerProps>> { thisprops; } { return <button onClick=thisonClick>foo: thispropsfoo </button> ; } const MyComponent = ;
Like react-redux, components need to be mounted within a Provider that injects a store.
// ./index.js // @flow;;;; const store = ;ReactDOM;
Testing
When testing React apps with Jest + Enzyme, often times we want to wait for some asynchronous computation to complete. Asynchronous actions are inspectable out of the box with a utility method called .toFinish()
:
// @flow;;;; ; { const store = ; const wrapper = ; await AppActionsinitialize; return wrapper store ;}
// ./AppActions.js; const initialize: Action<void Promise<void>> = ;
Why not Redux?
EFX started as a set of opinionated utility functions and classes built on top of redux + react-redux + redux-thunk for a simpler, more testable and more type-safe Redux stack.
Instead of publishing yet another set of opinionated design patterns and utility functions for Redux, we thought that there was enough noise in the ecosystem and that it would provide a better developer experience to put together an all-in-one solution. Therefore, we made the decision to start fresh, borrow the good parts of Redux and write a simpler yet more comprehensive state management solution.
In other words, EFX seeks to improve the developer experience for state management in the same way that Jest does for testing by packaging Mocha + Chai + Sinon into a single solution.
Good parts of Redux:
- Centralized state
- Unidirectional data flow
- With
redux-thunk
: Compositionality: a way todispatch
actions within actions - With
react-redux
: Simplicity: prop updating based on shallow equality
Bad parts of Redux:
- Message bus/Reducers: forces you to bend over backwards to change state via messages passing with no return values.
- Too low level -> Too many abstractions -> Terminology overload (reducers, constants, actions, action creators, middleware, etc.)
- Out-of-the-box experience very lackluster: (type safety? testability? async actions?)
- Overwhelming ecosystem, maybe too extensible/flexible.
- Community flow-typings are abysmal