Skip to content

Instantly share code, notes, and snippets.

@betafcc
Last active August 1, 2022 15:53
Show Gist options
  • Save betafcc/aa861809ebc0e77a4fdc04e649f75855 to your computer and use it in GitHub Desktop.
Save betafcc/aa861809ebc0e77a4fdc04e649f75855 to your computer and use it in GitHub Desktop.
Elm style useReducer
export type Msg = { GotText: Http.Result<Error, string> }
export type Model = { Failure: [] } | { Loading: [] } | { Success: string }
// copy of https://guide.elm-lang.org/effects/http.html
export const main = (
<ElmElement<Model, Msg>
init={() => [
{ Loading: [] },
Http.get({
url: 'https://elm-lang.org/assets/public-opinion.txt',
expect: Http.expectString(r => ({ GotText: r })),
}),
]}
update={(model, msg) =>
match(msg).of({
GotText: result =>
match(result).of<[Model, Cmd<Msg>]>({
Ok: fullText => [{ Success: fullText }, Cmd.none],
Err: _ => [{ Failure: [] }, Cmd.none],
}),
})
}
view={model =>
match(model).of({
Failure: () => <h1>I was unable to load your book</h1>,
Loading: () => <h1>Loading...</h1>,
Success: fullText => <pre>{fullText}</pre>,
})
}
/>
)
import { useEffect, useReducer } from 'react'
import type { UnionToIntersection } from 'type-fest'
export type Cmd<A> = (dispatch: (a: A) => void) => void
export namespace Cmd {
export const none = () => {}
}
export namespace Http {
export type Result<E, A> = { Err: E } | { Ok: A }
export const get =
<Msg,>(props: {
url: string
expect: (r: Response) => Promise<Msg>
}): Cmd<Msg> =>
dispatch =>
fetch(props.url).then(props.expect).then(dispatch)
export const expectString =
<Msg,>(toMsg: (result: Result<Error, string>) => Msg) =>
(r: Response) =>
r.text().then(
Ok => toMsg({ Ok }),
Err => toMsg({ Err })
)
}
export const match = <T,>(subject: T) => ({
of: <R,>(handler: {
[K in keyof UnionToIntersection<T>]: (value: UnionToIntersection<T>[K]) => R
}): R =>
handler[Object.keys(subject)[0] as keyof UnionToIntersection<T>](
Object.values(subject)[0]
),
})
export function ElmElement<Model, Msg>(props: {
update: (model: Model, msg: Msg) => [Model, Cmd<Msg>]
init: () => [Model, Cmd<Msg>]
view: (model: Model, dispatch: (msg: Msg) => void) => JSX.Element
}) {
const [[model, cmd], dispatch] = useReducer(
(m: [Model, Cmd<Msg>], msg: Msg) => props.update(m[0], msg),
null,
props.init
)
useEffect(() => {
cmd(dispatch)
}, [cmd])
return props.view(model, dispatch)
}
import ReactDOM from 'react-dom/client'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(main)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment