Skip to content

Instantly share code, notes, and snippets.

@jakobz
Last active November 9, 2019 22:07
Show Gist options
  • Select an option

  • Save jakobz/047b52bccc7d19bb24904fe5e981aeef to your computer and use it in GitHub Desktop.

Select an option

Save jakobz/047b52bccc7d19bb24904fe5e981aeef to your computer and use it in GitHub Desktop.
Typed Redux wrapper
import { Reducer } from "redux";
import { Dispatch } from "react-redux";
export type ReducerCur<TState, TPayload = any> = (payloyoad: TPayload) => (state: TState) => TState;
export type ActionCreator<TPayload> = (payload: TPayload) => ({ type: string } & TPayload);
type GetActionCreator<TState, T> = T extends ReducerCur<TState, infer TPayload> ? ActionCreator<TPayload> : void;
export type ActionCreatorsSet<TState, T> = { [TName in keyof T]: GetActionCreator<TState, T[TName]> };
export interface Bundle<TState, TReducersSet> {
reducer: Reducer<TState, any>;
actionCreators: ActionCreatorsSet<TState, TReducersSet>;
bind(dispatch: Dispatch<TState>): ActionCreatorsSet<TState, TReducersSet>;
}
export interface BuildReducerOptions {
prefix?: string;
}
export function buildReducer<TState, TReducersSet>(
initialState: TState,
reducersSet: TReducersSet,
options?: BuildReducerOptions
): Bundle<TState, TReducersSet> {
const effectiveOptions: BuildReducerOptions = options || {};
const reducers: Record<string, ReducerCur<TState>> = {};
const actionCreators: any = {};
const actionDispatchers: any = {};
Object.keys(reducersSet).forEach((name: string) => {
let type = name.replace(/(?:^|\.?)([A-Z])/g, (x, y) => { return "_" + y.toLowerCase(); }).replace(/^_/, "").toUpperCase();
if (effectiveOptions.prefix) {
type = effectiveOptions.prefix + '_' + type;
}
reducers[type] = (reducersSet as any)[name];
actionCreators[name] = (payload: any) => ({ type, ...payload });
actionDispatchers[name] = (dispatch: Dispatch<TState>) => (payload: any) => dispatch({ ...payload, type });
});
function reducer(state: any, action: any) {
if (typeof state === 'undefined') {
return initialState;
}
const reducerCar: ReducerCur<any, any> = (reducers as any)[action.type];
const newState = reducerCar ? reducerCar(action)(state) : state;
return newState;
}
function bind(dispatch: Dispatch<TState>) {
const actionEmitters: any = {};
Object.keys(actionDispatchers).forEach(key => actionEmitters[key] = actionDispatchers[key](dispatch));
return actionEmitters;
}
return { reducer, actionCreators, bind };
}
// usage in another file
import { buildReducer, ReducerCur } from '../reduxHelpers';
import { SupportState, SupportTabs, SupportErrors } from './supportModel';
import * as models from '../../../server/models';
import { ImageFile } from 'react-dropzone';
import { uniqueArray } from '../../common/index';
const initialState: SupportState = {
inputValue: '',
dropzoneActive: false,
files: [],
answers: [],
isSearching: false,
isError: false,
currentTab: SupportTabs.main,
selectedAnswer: null,
validationErrors: [],
tryAgainHandler: () => { },
attemptsTryAgainCounter: 0,
thanksMessage: '',
};
const input = (a: { inputValue: string }) => (s: SupportState) => ({ ...s, inputValue: a.inputValue });
const clearInput = ({ }) => input({ inputValue: '' });
const setDropzoneStatus = (a: { isActive: boolean }) => (s: SupportState) => ({ ...s, dropzoneActive: a.isActive });
const dropzoneIsActive = ({ }) => setDropzoneStatus({ isActive: true });
const dropzoneIsNotActive = ({ }) => setDropzoneStatus({ isActive: false });
const addFiles = (a: { files: ImageFile[] }) => (s: SupportState) => ({ ...s, files: [...a.files, ...s.files], dropzoneActive: false });
const removeFile = (a: { file: ImageFile }) => (s: SupportState) => ({ ...s, files: s.files.filter(f => f !== a.file) });
const resetForm = ({ }) => (s: SupportState) => (
{ ...s, files: [], inputValue: '', answers: [], dropzoneActive: false, errors: [], isSearching: false, isError: false, attemptsTryAgainCounter: 0 }
);
const cleanErrors = ({ }) => (s: SupportState) => ({ ...s, errors: [], isError: false});
const startSearching = ({ }) => (s: SupportState) => ({ ...s, isSearching: true, isError: false });
const receiveKBAnswers = (a: { answers: models.KbAnswer[] }) => (s: SupportState) => ({ ...s, answers: a.answers, isSearching: false, attemptsTryAgainCounter: 0 });
// ....
const bundle = buildReducer(
initialState,
{
input, clearInput, dropzoneIsActive, dropzoneIsNotActive, addFiles, removeFile, resetForm,
startSearching, receiveKBAnswers, showMainTab, showAnswerTab, showErrorTab, showSuccessTab,
addError, removeError, setTryAgainHandler, cleanErrors, setThanksMessage
},
{ prefix: "SUPPORT" }
);
export const supportActions = bundle.actionCreators;
export const supportBindActions = bundle.bind;
export const supportReducer = bundle.reducer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment