react-epics
v0.2.0
Published
Predictable state management solution for React applications.
Downloads
2
Maintainers
Readme
react-epics
Predictable state management solution for React applications. React-Epics is a valuable tool for organizing your state. Inspired by Redux it uses the power of RxJS observables and react hooks to manage state changes.
🚀 Epics
An Epic is a function which takes an stream of actions (action$
), an stream of the current state (state$
), and function returns an Observable of the new state.
Once you're inside your Epic, use any Observable patterns you desire as long as any output from the final, returned stream, is the new state.
This idea, which is based on redux allows us to use all RxJS awesome abilities in our React components with a powerful API.
🛠 Installation
react-epics
needs both react and rxjs as peer dependencies.
npm
npm install react-epics rxjs react
yarn
yarn add react-epics rxjs react
🔧 Usage
1. Create your state types: src/types.ts
export type ICounter = {
value: number;
};
2. Setup your epic: src/epics/counter.ts
import { map } from 'rxjs/operators';
import { merge } from 'rxjs';
import { Epic } from 'react-epics';
import { ICounter } from '../types';
export const initialState: ICounter = {
value: 0,
};
export const epic: Epic<ICounter> = ({ ofType }) => {
// plus action
const plus$ = ofType('counter/plus').pipe(
map(([action, state]) => {
return { value: state.value + action.payload };
}),
);
// minus action
const minus$ = ofType('counter/minus').pipe(
map(([action, state]) => {
return { value: state.value - action.payload };
}),
);
// merge all actions into one observable
return merge(plus$, minus$);
};
3. Register your epics in the store: src/App.tsx
import * as React from 'react';
import { createStore, StoreProvider } from 'react-epics';
// epics
import * as counter from './epics/counter';
// components
import Counter from './components/Counter';
// store
const store = createStore({
counter,
});
const App: React.FC = () => {
return (
<StoreProvider store={store}>
<div className="App">
<Counter />
</div>
</StoreProvider>
);
};
4. Use it on any component: src/components/Counter.tsx
import * as React from 'react';
import { useDispatch, useEpic } from 'react-epics';
import { ICounter } from '../types.ts';
const Counter: React.FC = () => {
const dispatch = useDispatch();
const { value } = useEpic<ICounter>('counter'); // name of the registered epic
// actions
const plus = () =>
dispatch({
type: 'counter/plus',
payload: 1,
});
const minus = () =>
dispatch({
type: 'counter/minus',
payload: 1,
});
return (
<div className="counter-wrapper">
<h2>Couter: {value}</h2>
<button onClick={plus}>+</button>
<button onClick={minus}>-</button>
</div>
);
};
📖 Documentation - Deprecated!!!
useEpic()
This is a React hook that allows us to use RxJS Observables for state management.
type State = {...}
type Payload = {...}
type Dispatch = (action: Action<Payload>) => void;
type useEpic = (
epic: Epic<Payload, State, Dependencies>,
initialState: State,
dependecies?: Dependencies,
) => [State, Dispatch, Error | null]
The useEpic()
hook, accepts an epic function, the initial state and an optional dependency object. It returns an array with the current state, dispatch callback and a nullable error.
epic()
An Epic is a function which takes an stream of actions (action$
), an stream of the current state (state$
), and an optional object of dependencies
, this function returns an Observable of the new state.
The idea of the Epic comes from redux-observable, but because redux-observable is redux middleware, the observable returned from the Epic emits new actions, react-epics
expects the Epic to return an observable of state updates.
type State = {...}
type Payload = {...}
type Action = {
type: string;
payload: Payload;
};
type Action$<P> = Observable<Action>;
type State$ = Observable<State>;
type Epic<Payload, State, Dependencies = {}> = (
actions$: Action$<Payload>,
state: State$,
dependecies: Dependencies,
) => State$;
Operators
ofType
This operator filters the actions emited by the actions obserbable (action$
) depeding if the emited action type match with the
action type parameter.
type ofType(actionType: string) => OperatorFunction<Action<Payload>>
example:
type State = 'foo' | 'bar';
type Payload = {...}
const FooBarEpic: Epic<Payload, State> = (action$) => {
const foo$ = action$.pipe(
ofType('FOO'),
mapTo('foo')
);
const bar$ = action$.pipe(
ofType('BAR'),
mapTo('bar')
);
return merge(foo$, bar$);
}
mapAction
The mapAction operator maps the emited value if the action match the action type parameter, notice that it doesn't apply a filter so the next actions emited won't be filtered out of the stream.
type mapCallback(action: Action<Payload>) => State
type mapAction(actionType: string, mapCallback) => OperatorFunction<State>
example:
type State = 'foo' | 'bar';
type Payload = {...}
const FooBarEpic: Epic<Payload, State> = (action$) => {
return action$.pipe(
mapAction(
'FOO', (action) => 'foo'
),
mapAction(
'BAR', (action) => 'bar'
),
);
}