Skip to content

Instantly share code, notes, and snippets.

@r17x
Created September 4, 2024 19:42
Show Gist options
  • Save r17x/177f5114761f45d3c5aac0e025ede08f to your computer and use it in GitHub Desktop.
Save r17x/177f5114761f45d3c5aac0e025ede08f to your computer and use it in GitHub Desktop.
TypeScript ADT & ts-pattern
import * as React from 'react'
import { match, P } from 'ts-pattern'
// Playground https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcA5FDvmQFCiSxwDecIyMuAFgDRwAKcAL5wiJcjFQBaMOxhYoAO1o1cEBangKIAeTBwAvHAAUASgMA+ZoJo0YATzBY4AOQgwAgqgDWWACYHmL2AFXwAuclcPbz8ya1V1eEjPH39DU3Ck6NTLIyYgkPCyTJTYkxt7RzgASQVgGGBkABsAGQhkX2CAcwCWfLDyGrqGlraOhU7YlTUNatr6ptb2roDTOHDB+ZGl8f0c3uD+sg3hxbGJwTLbBydTroAeABVLQ32C8lvxsl5fdmRwh6EUwScA+3UMj245iMPxgfweJnCoMezz2cD6hVBXzgMOQQkuFScADFkMBGgBXSh3ACiATJCi8WgA7gpnnBmGiDoViaSKVgsfJiFBwjS4tN4NzyZQAtTafSmSyjALoMK4GZwhLedSUcZXocNZR+VBBXjytc4ABlMm4XBYVCoZEBdno8iW6221BYnH-QHxGaum126UPSHQ36heHhf3uh0WHUct5kKN2z2-E1XSrYEBuLAAEV+ELgNMMdIZEGZzxoAB8XG5kn4q7MhgtRl0G0ing39VgtQ2k-aO+mnObYXIApns3nYXcAKqoeTmGy++BsPTg4NU7juKFQfrjuST5AQqnmXgEcKh2Hhsy7OARzBYLP7-Pr7VsDicIw7spsgB0jLqH40GybJ5JycAAETFH44GCNwQHAVBvjwd+cB-gBRjwcBoFvHwP50sAahGGQmK8EccwnC2nwmLBmFssczbbJ0yHwWhMCAcBTpgWQXaxHBHGrDeO58aqLH-mxGEcdhhx9rxtG5DiFyxn2RgEBeyAmChKE-lgAAenDIGSGjAAAblgpimpUs7yD08EKMgIBYOEGhQF0ADc8GGfIdkOU5MAueM7lssgxm-EKcDOW5NDWECMwyFAc5WVAKx-HAJbygicCJbGb5cEY6miehtG4XOjRYPguRwN5jn8D+EXjLwnmKPZ1XFX5XS8MgnQtT+ChkiAABG8gdSFsJha1-ndBcwlskYjXhIl16WI1wkoQA9KtcAAOJ1AAEmS-WpXOSU4nAcVzgV4m0WyxVYKV5VMFdwFVeE43tY9bKNBAnTBC9tVtfV71wP1BG-XVnTTRxKAjcgUAAPoUo0oP-eDj1TY9FXBaF8NQIjUOhbwn3fQo4SNVVvDAxAvBVXisa5IDpPNRDHFk4DFNM8BmOjezFy0VpUB0uZMXwO4YCrsYi3MPBS5wAA2ho7BYLwc4wMOCsALpjlQMB4XOqtyHcetYFCiGXGy2B4NrnlUgQBBlTAEnAQJlg5R+8tyChHGsexkMgc6kG1lkMHszNEsPT7HHK4bRj0VsZwe+HtvvkRq3IGAwCrY1ZDxz7P5sVgCifrGUA-gAVqghEaXA60iCSjTIP1pWA7+ecF2dWALVXG0ELX9eN+HwG55w+dGH22eQ4Pw+RyOWBj57uDsLlTg3mH-fAVPCtGF2ioaU3NOA9YPsmD+bhD1A-5zkYWi6PHENy1gKvT0r08-n0avMfBlAwBSCisAvrvT1pMS3tfZgVwvhQiZEmyxy6FiIoAcShmFguLWMdwOjGXMOaHwpUYBqB-Hgn8dxVpoIXGyQBhVJLOjAbUCBMlSKYmohDIwqAJaoJMuYHat1PrMFQD+HEPVmqCEIcQ5iv4gEOxAQmHiNF+IsOIeaaAUA7C8AAOpOHngoLQ8BUCcDLHAXAFJKAKHgI1OAwQCDQDfARH+5ikp2AgGSOAgA+DcAJ67Qi2EiNQifeQ58zJO0qmSRojQyiCCAA
const noOp = () => {}
type NotAsked = {kind: 'NotAsked'}
const NotAsked = (): NotAsked => ({kind: 'NotAsked'})
type InitialLoading = { kind: 'InitialLoading'}
const InitialLoading = () : InitialLoading=> ({ kind: 'InitialLoading'})
type Loading<T> = { kind: 'Loading', data: T }
const Loading = <T,>(data:T): Loading<T> => ({ kind: 'Loading', data })
type Failure<E = unknown> = { kind: 'Failure', error: E }
const Failure = <E = unknown>(error: E ) : Failure<E> => ({ kind: 'Failure', error })
type Success<T> = { kind: 'Success', data: T }
const Success = <T,>(data:T): Success<T> => ({ kind: 'Success', data })
type RemoteData<T, E = unknown> =
| NotAsked
| InitialLoading
| Loading<T>
| Failure<E>
| Success<T>
type State = RemoteData<User>
const map = <T,E,A>(rd: RemoteData<T,E>, f: (data:T) => T): RemoteData<T,E> => match(rd)
.with(
{kind: "NotAsked"},
NotAsked
)
.with(
{kind: P.union('Loading', 'InitialLoading')},
InitialLoading
)
.with(
{ kind: 'Failure'},
() => rd,
)
.with(
{kind: 'Success'},
({data}) => Success(f(data))
)
.exhaustive()
type User = {
name: string;
username: string;
avatar: string;
}
const parseUser = (a: unknown): User => match(a)
.with(
P.select({ name: P.string, username: P.string, age: P.number, avatar: P.string }),
(user: User) => user,
)
// GitHub user data parse
.with(
P.select({
name: P.string,
login: P.string,
bio: P.string,
avatar_url: P.string,
}),
({ avatar_url: avatar, login: username, bio, name }) => ({
username,
name,
bio,
avatar,
})
)
.run()
const App = () => {
const [state, setState] = React.useState<State>(NotAsked)
React.useEffect(
() => match(state)
.with(
{kind: "NotAsked"},
() => {
setState(InitialLoading)
fetch('/api/user')
.then(r => r.json()) // failable
.then(parseUser) // failable
.then(Success)
.then(setState)
.catch(e => {
setState(Failure(e))
})
}
).otherwise(noOp)
,
[setState, state.kind]
)
return match(state)
.with(
{kind: P.union('InitialLoading', 'NotAsked') }, () => <div>Skeleton....</div>
)
.with(
{kind: P.union('Success', 'Loading')},
(s) => <div>Hello {s.data.name}</div>
)
.with(
{kind: 'Failure'},
() => <div>Sorry, We cannot show current user information for you 🥹</div>
)
.otherwise(() => null)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment