Skip to content

Commit 39f2fd7

Browse files
committed
feat: add "match" util
1 parent 6b4219b commit 39f2fd7

File tree

2 files changed

+88
-11
lines changed

2 files changed

+88
-11
lines changed

src/pages/_error.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import { Box } from 'components/atoms/Box'
22
import type { NextPage } from 'next'
3+
import { match } from 'utils/match'
34

45
type Props = {
56
statusCode?: number
67
}
78

89
const Error: NextPage<Props, unknown> = ({ statusCode }) => {
9-
const content = (() => {
10-
if (statusCode === 404) return 'Oops, missing page'
11-
if (statusCode) return `An error ${statusCode} occurred on the server`
12-
else return 'An error occurred on the client'
13-
})()
10+
const content = match(statusCode)
11+
.with(
12+
code => code === 404,
13+
() => 'Oops, missing page'
14+
)
15+
.with(
16+
code => !!code,
17+
() => `An error ${statusCode} occurred on the server`
18+
)
19+
.otherwise(() => 'An error occurred on the client')
1420

1521
return (
1622
<Box
@@ -27,13 +33,23 @@ const Error: NextPage<Props, unknown> = ({ statusCode }) => {
2733
}
2834

2935
Error.getInitialProps = ({ err, res }): Props => {
30-
const statusCode = (() => {
31-
if (err) return err.statusCode
32-
if (res) return res.statusCode
33-
else return 404
34-
})()
36+
const statusCode = match({
37+
error: err,
38+
response: res,
39+
})
40+
.with(
41+
({ error }) => !!error,
42+
({ error }) => error?.statusCode
43+
)
44+
.with(
45+
({ response }) => !!response,
46+
({ response }) => response?.statusCode
47+
)
48+
.otherwise(() => 404)
3549

36-
return { statusCode }
50+
return {
51+
statusCode,
52+
}
3753
}
3854

3955
export default Error

src/utils/match.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// 🙏 shoutout to -> https://github.com/gvergnaud/ts-pattern
2+
3+
type Union<a, b> = [b] extends [a] ? a : [a] extends [b] ? b : a | b
4+
5+
type Match<T, InferredOutput = never> = {
6+
/**
7+
* if the `predicate` func returns true, the value returned by the `handler` will be the one returned when calling `otherwise`
8+
*/
9+
with: <O>(
10+
predicate: (value: T) => boolean,
11+
handler: (value: T) => O
12+
) => Match<T, Union<InferredOutput, O>>
13+
/**
14+
* takes a function allowing one to return a fallback value in case no match were found
15+
*/
16+
otherwise: <O>(fallback: () => O) => Union<InferredOutput, O>
17+
}
18+
19+
/**
20+
* Entry point to create some sort of a matching expression
21+
*
22+
* It returns a Match builder, on which you can chain several .with(predicate, handler) clauses
23+
*
24+
* @example
25+
*
26+
* type Status = 'idle' | 'loading' | 'success' | 'error'
27+
*
28+
* const result = match(status)
29+
* .with(s => s === 'idle', (s) => <p>is {s}</p>) // s === "idle"
30+
* .with(s => s === 'loading', (s) => <Loader />) // s === "loading"
31+
* .otherwise(() => <p>either in success or in error</p>) // s === "success" | "error"
32+
*
33+
*/
34+
/* eslint-disable fp/no-mutation, fp/no-let */
35+
export function match<T, U = never>(value?: T) {
36+
let outputValue: any
37+
38+
function self(passedValue: T) {
39+
const matchBuilder = {} as Match<T, U>
40+
41+
function with_<O>(
42+
predicate: (value: T) => boolean,
43+
handler: (value: T) => O
44+
): Match<T, Union<U, O>> {
45+
if (!outputValue && predicate(passedValue)) outputValue = handler(passedValue)
46+
47+
return self(passedValue) as unknown as Match<T, Union<U, O>>
48+
}
49+
50+
function otherwise<O>(fallback: () => O) {
51+
return outputValue || fallback()
52+
}
53+
54+
matchBuilder.with = with_
55+
matchBuilder.otherwise = otherwise
56+
57+
return matchBuilder
58+
}
59+
60+
return self(value as T)
61+
}

0 commit comments

Comments
 (0)