Skip to content

Instantly share code, notes, and snippets.

@fnimick
Forked from itsMapleLeaf/README.md
Last active April 15, 2022 22:00
Show Gist options
  • Save fnimick/479bbfeb1d0649dc63603d478797cf2e to your computer and use it in GitHub Desktop.
Save fnimick/479bbfeb1d0649dc63603d478797cf2e to your computer and use it in GitHub Desktop.
Typed remix helpers

Typed helpers for low-boilerplate type inference with Remix data.

  • I suffix them with *Typed so I don't accidentally import the core remix helpers.
  • This doesn't support regular Response objects to prevent accidentally using them with the typed helpers.
import type { DataFunctionArgs } from "@remix-run/server-runtime"
import { Form } from "remix"
import {
jsonTyped,
redirectTyped,
useActionDataTyped,
useLoaderDataTyped,
} from "~/remix-typed"
let message = "hi"
export async function loader({ request }: DataFunctionArgs) {
return jsonTyped({ message })
}
export async function action({ request }: DataFunctionArgs) {
const body = new URLSearchParams(await request.text())
const newMessage = body.get("message")
if (typeof newMessage !== "string") {
return jsonTyped({ error: "no message" }, 400)
}
message = newMessage
return "" // stay on the same page
}
export default function Page() {
const { message } = useLoaderDataTyped<typeof loader>()
const { error } = useActionDataTyped<typeof action>() ?? {}
return (
<main>
<p>{message}</p>
<Form method="post">
<input name="message" />
<button type="submit">Submit</button>
</Form>
{error && <p style={{ color: "crimson" }}>{error}</p>}
</main>
)
}
import type { DataFunctionArgs } from "@remix-run/server-runtime"
import { json, redirect, useActionData, useFetcher, useLoaderData } from "remix"
type MaybePromise<Value> = Value | PromiseLike<Value>
type JsonValue =
| string
| number
| boolean
| null
| { [Key in string]?: JsonValue }
| JsonValue[]
| undefined
export type TypedResponse<Data extends JsonValue> = Omit<Response, "json"> & {
json(): Promise<Data>
}
// prettier-ignore
type DataFunctionTyped<Data extends JsonValue> = (args: DataFunctionArgs) =>
MaybePromise<Data | TypedResponse<Data>>
type InferLoaderData<DataFunction> = DataFunction extends DataFunctionTyped<
infer Data
>
? Data
: never
export function responseTyped(
body?: BodyInit | null,
init?: ResponseInit | number,
) {
return new Response(
body,
typeof init === "number" ? { status: init } : init,
) as TypedResponse<never>
}
export function jsonTyped<Data extends JsonValue>(
data: Data,
init?: ResponseInit | number,
) {
return json(data, init) as TypedResponse<Data>
}
export function redirectTyped(url: string, init?: ResponseInit | number) {
return redirect(url, init) as TypedResponse<never>
}
export function useLoaderDataTyped<
DataFunction extends DataFunctionTyped<JsonValue>,
>() {
return useLoaderData<InferLoaderData<DataFunction>>()
}
export function useActionDataTyped<
DataFunction extends DataFunctionTyped<JsonValue>,
>() {
return useActionData<InferLoaderData<DataFunction>>()
}
export function useFetcherTyped<
DataFunction extends DataFunctionTyped<JsonValue>,
>() {
return useFetcher<InferLoaderData<DataFunction>>()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment