Skip to content

Instantly share code, notes, and snippets.

@benceg
Last active March 4, 2023 14:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benceg/f85e5bfafb78b238d97d10ea3b209c67 to your computer and use it in GitHub Desktop.
Save benceg/f85e5bfafb78b238d97d10ea3b209c67 to your computer and use it in GitHub Desktop.
Elm Pattern in React & TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
ComponentProps,
Dispatch,
FC,
ReactElement,
Reducer,
ReducerAction,
ReducerState,
useReducer,
} from "react";
export const ElmPattern = <T extends Reducer<any, any>>(props: {
children: (
props: ReducerState<T> & { dispatch: Dispatch<ReducerAction<T>> }
) => ReactElement;
reducer: T;
initialState: ReducerState<T>;
}): ReactElement => {
const [state, dispatch] = useReducer(props.reducer, props.initialState);
return props.children({ dispatch, ...state });
};
export const elmify =
<C extends FC<any>, T extends ComponentProps<C>>(Component: C) =>
(props: T) =>
<Component {...props} />;
import { elmify, ElmPattern } from "./ElmPattern";
import { FC, Reducer } from "react";
interface UserState {
name: string;
age: number;
}
const userReducer: Reducer<
UserState,
{ type: "incremented_age" } | { type: "changed_name"; nextName: string }
> = (state, action) => {
switch (action.type) {
case "incremented_age":
return {
name: state.name,
age: state.age + 1,
};
case "changed_name":
return {
name: action.nextName,
age: state.age,
};
}
};
export const Component: FC<UserState> = ({ name, age }) => (
<>
{name} is {age} years old
</>
);
const children = elmify(User);
const User: FC = () => (
<ElmPattern reducer={userReducer} initialState={{ name: "", age: 0 }}>
{children}
</ElmPattern>
);
export default User;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment