Consequential redux actions
A small API extracting the goodness of https://github.com/edumentab/talks-redux-patterns by @krawaller.
import {actionCreatorFactory} from 'conduxion'
import produce from 'immer'
import {AppActionMould} from '../../../state.types';
import {STATE_KEY} from '../authentication.state';
import {setAppError} from '../../ui';
type SetIsAuthenticatedPayload = {
isAuthenticated: boolean
}
export type SetIsAuthenticatedAction = AppActionMould<'SET_IS_AUTHENTICATED', SetIsAuthenticatedPayload>
export const [setIsAuthenticated, isSetIsAuthenticated] = actionCreatorFactory<SetIsAuthenticatedAction>({
type: 'SET_IS_AUTHENTICATED',
reducer(state, payload) {
const {isAuthenticated} = payload;
return produce(state, draft => {
draft[STATE_KEY].isAuthenticated = isAuthenticated;
})
},
consequence({action, dispatch}) {
const {isAuthenticated} = action.payload;
if (!isAuthenticated) {
dispatch(setAppError('Not authenticated'));
return;
}
}
});
An alternative approach to redux where we put actions first, and view state as a consequence of them.
Instead of creating singleton reducers responsible for a single state slice, we create reducers for each individual action that can act across the entire state.
Should an action have consequences, they are declared on the action itself (as an event-emitter).
$ npm i -S conduxion
Please note that redux
is a peer-dependency.
Using pre-fab factories.
import {Store} from 'redux';
import {makeStore, RootReducer, ConduxionAction} from 'conduxion';
type AppState = {
// whatever you want
[key: string]: any
}
type AppDependencies = {}
type AppAction = ConduxionAction<AppState, AppDependencies>
const INITIAL_APP_STATE: AppState = {}
const rootReducer: RootReducer<AppState, AppAction, AppDependencies> = (state = INITIAL_APP_STATE, action) => action.reducer
? action.reducer(state, action.payload)
: state;
const store: Store<AppState, AppAction> = makeStore<AppState, AppAction, AppDependencies>(
rootReducer,
INITIAL_APP_STATE
);
export default store;
For your convenience, we also expose a dev version that automatically connects to Redux DevTools
import {Store} from 'redux';
import {createActionLogMiddleware, makeDevStore, RootReducer, ConduxionAction} from 'conduxion';
type AppState = {
// whatever you want
[key: string]: any
}
type AppDependencies = {}
type AppAction = ConduxionAction<AppState, AppDependencies>
const INITIAL_APP_STATE: AppState = {}
const rootReducer: RootReducer<AppState, AppAction, AppDependencies> = (state = INITIAL_APP_STATE, action) => action.reducer
? action.reducer(state, action.payload)
: state;
const actionLog: any[] = [];
const store: Store<AppState, AppAction> = makeDevStore<AppState, AppAction, AppDependencies>(
rootReducer,
INITIAL_APP_STATE,
{
// createActionLogMiddleware mutates actionLog with each action fired
additionalMiddleware: [createActionLogMiddleware(actionLog)]
}
);
export default store;
Using the actionCreatorFactory() factory function.
import {actionCreatorFactory, ConduxionActionMould} from 'conduxion'
import {AppState, AppDependencies} from '../somewhere'
import {setAppError} from '../somewhere-else'
type AppActionMould<T extends string, P> = ConduxionActionMould<
T,
P,
AppState,
AppDependencies
>
type SetIsAuthenticatedPayload = {
isAuthenticated: boolean
}
export type SetIsAuthenticatedAction = AppActionMould<'SET_IS_AUTHENTICATED', SetIsAuthenticatedPayload>
export const [setIsAuthenticated, isSetIsAuthenticated] = actionCreatorFactory<SetIsAuthenticatedAction>({
type: 'SET_IS_AUTHENTICATED',
// optional
reducer(state, payload) {
const {isAuthenticated} = payload;
return {
...state,
isAuthenticated
}
},
// optional
consequence({action, dispatch}) {
const {isAuthenticated} = action.payload;
if (!isAuthenticated) {
dispatch(setAppError('Not authenticated'));
return;
}
}
});
Creates a conduxion redux store with consequence middleware applied.
import {Store} from 'redux'
import {ConduxionAction, RootReducer, MakeStoreOptions} from 'conduxion';
export function makeStore<State extends object, A extends ConduxionAction<State, Dependencies>, Dependencies extends object>(
rootReducer: RootReducer<State, A, Dependencies>,
initialState: State,
opts: MakeStoreOptions<State, Dependencies> = {}
): Store<State, A>;
Creates a conduxion redux store with consequence middleware as well as Redux DevTools middleware applied.
import {Store} from 'redux'
import {ConduxionAction, RootReducer, MakeStoreOptions} from 'conduxion';
export function makeStore<State extends object, A extends ConduxionAction<State, Dependencies>, Dependencies extends object>(
rootReducer: RootReducer<State, A, Dependencies>,
initialState: State,
opts: MakeStoreOptions<State, Dependencies> = {}
): Store<State, A>;
Factory function returning an ActionCreator and ActionGuard for a gived redux action.
import {
Action,
ActionType,
ActionReducer,
ActionState,
ActionPayload,
Consequence,
ActionDeps,
ConduxionAction,
ActionCreator,
ActionGuard
} from 'conduxion'
type CreatorBlueprint<A extends Action<string, any, any, any>> = {
type: ActionType<A>
reducer: ActionReducer<ActionState<A>, ActionPayload<A>>
isError?: boolean
consequence?: Consequence<ActionState<A>, ActionDeps<A>, ActionPayload<A>>
}
export default function actionCreatorFactory<A extends ConduxionAction<any, any>>(
blueprint: CreatorBlueprint<A>
): [ActionCreator<A>, ActionGuard<A>]
There are a lot of types exposed by conduxion, here are a few of them. Please see the root types as well as the core types for a comprehensive list.
Redux root reducer for conduxion.
import {ConduxionAction} from 'conduxion'
export type RootReducer<
State extends object,
A extends ConduxionAction<State, Dependencies>,
Dependencies extends object
> = (state: State | undefined, action: A) => State;
The base interface for all conduxion actions. Enables use of consequence methods.
import {ActionReducer, Consequence} from 'conduxion';
export interface Action<
Type extends string,
Payload extends any,
State extends object,
Dependencies extends object> {
type: Type
error?: boolean
sender?: string
reducer?: ActionReducer<State, Payload>
payload: Payload
consequence?: Consequence<State, Dependencies, Payload> | Consequence<State, Dependencies, Payload>[]
}
An action consequence method.
import {ConsequenceAPI} from 'conduxion'
export type Consequence<State extends object, Dependencies extends object, Payload extends any = any> = ((
api: ConsequenceAPI<State, Dependencies, Payload>
) => void) & {
name: string,
displayName?: string
}
Generic representation of a conduxion action.
import {Action} from 'conduxion';
export type ConduxionActionMould<
Type extends string,
Payload,
State extends object,
Dependencies extends object
> = Action<Type, Payload, State, Dependencies>
Additional configuration for makeStore and makeDevStore.
import {Middleware} from 'redux';
import {ConsequenceGetter, Consequence} from 'conduxion';
export type MakeStoreOptions<State extends object, Dependencies extends object> = {
additionalMiddleware?: Middleware[]
dependencies?: Dependencies
consequenceGetter?: ConsequenceGetter<State, Dependencies>
initConsequence?: Consequence<State, Dependencies>
}