Skip to content

Instantly share code, notes, and snippets.

@bpander
Last active August 28, 2019 03:42
Show Gist options
  • Save bpander/2e409687208a4138fcd8421cf8bfc80b to your computer and use it in GitHub Desktop.
Save bpander/2e409687208a4138fcd8421cf8bfc80b to your computer and use it in GitHub Desktop.
import { Reducer, AnyAction, Dispatch } from 'redux';
import { PartialRecord } from '@src/lib/PartialRecord';
export interface Action<T, TPayload> {
type: T;
payload: TPayload;
}
export type ActionConfig<T, P> = (payload: P) => Action<T, P>;
export const configureActionsWith = <S>(initialState: S, prefix = '') => {
const handlerMap: PartialRecord<string, (payload: any) => (state: S) => S> = {};
const configureAction = <P>(type: string, reduce: (payload: P) => (state: S) => S) => {
const prefixedType = prefix + '/' + type;
handlerMap[prefixedType] = reduce;
return (payload: P) => ({ type: prefixedType, payload });
};
// const configureAsyncAction = <P>(
// type: string,
// asyncReduce: (payload: P) => Promise<(state: S) => S>,
// setResult: (result: Result) => (state: S) => S,
// ) => {
// const prefixedType = prefix + '/' + type;
// handlerMap[prefixedType] = (status: AsyncStatus) => state => state;
// return (payload: P) => async (dispatch: Dispatch) => {
// return {} as Result;
// // TODO: Can just return success or fail+error?
// };
// };
const reducer: Reducer<S, AnyAction> = (state = initialState, action) => {
const handler = handlerMap[action.type];
if (handler) {
return handler(action.payload)(state);
}
return state;
};
return { configureAction, configureAsyncAction, reducer };
};
enum AsyncStatus {
Ready,
Pending,
Rejected,
Resolved,
}
type Result =
| { status: AsyncStatus.Ready }
| { status: AsyncStatus.Pending }
| { status: AsyncStatus.Resolved }
| { status: AsyncStatus.Rejected; error: Error }
interface Thing { id: string; name: string }
interface ThingState {
things: Thing[];
fetchThingsResult: Result;
updateThingResult: Result;
}
const initialState: ThingState = {} as any;
const { configureAsyncAction } = configureActionsWith<ThingState>(initialState, 'THING');
export const fetchThings = /*memoize(*/
configureAsyncAction<{ userId: string }>(
payload => ThingService.getThingsForUser(payload.userId),
configureAction('SET_THINGS', l.k('things').set),
configureAction('FETCH_THINGS', l.k('fetchThingsStatus').set),
),
// { maxAge: 1000 * 60 * 60 },
// );
// TODO: How to clear cache on component mount? A: Use `key` without memoization
props.fetchThings({ userId: props.params.userId });
type AsyncState =
| { status: 'ready' }
| { status: 'pending'; args: any[] }
| { status: 'success'; time: number }
| { status: 'error'; error: Error }
interface Thing { id: string; name: string }
interface ThingState { map: Record<string, Thing>; fetchState: AsyncState }
export const fetchThings = configureAsyncAction(ThingService.fetchThings, [
configureAction(
'REQUEST_THING',
() => state => ({ ...state, fetchState: asyncPending() }),
),
configureAction(
'RECEIVE_THING',
thing => state => ({
...state,
map: { ...state.map, [thing.id]: thing },
fetchState: asyncResolved(),
}),
),
configureAction(
'HANDLE_THING',
error => state => ({ ...state, fetchState: asyncError(error) }),
),
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment