remix-typedjson
This package is a replacement for superjson
to use in your Remix app. It handles a subset
of types that superjson
supports, but is faster and smaller.
NOTE: Although faster, remix-typedjson
is nowhere near as flexible as superjson
. It only supports a subset of types with no extensibility. If you need the advanced features of superjson
, then I definitely recommend it.
Example site: https://remix-typedjson-example-production.up.railway.app/
Example repo: https://github.com/kiliman/remix-typedjson-example
The following types are supported:
Date
BigInt
Set
Map
RegExp
undefined
Error
NaN
Number.POSITIVE_INFINITY
Number.NEGATIVE_INFINITY
🚧 Work In Progress
Sets and Maps currently only support string keys and JSON serializable values. Complex types coming soon.
🛠 How to Use with Remix
In order to get full-type fidelity and type inference, you must be on Remix
v1.6.5+. You will also need to import the following replacement functions.
typedjson
Installation
npm i remix-typedjson
Replacement for Remix json
helper. It also supports the optional ResponseInit
, so you can return headers, etc.
Make sure your loader
and action
use the new declaration format:
❌ export const loader: LoaderFunction = async ({request}) => {}
❌ export const action: LoaderFunction = async ({request}) => {}
✅ export const loader = async ({request}: DataFunctionArgs) => {}
✅ export const action = async ({request}: DataFunctionArgs) => {}
✅ export async function loader({request}: DataFunctionArgs) {}
✅ export async function action({request}: DataFunctionArgs) {}
Usage
return typedjson(
{ greeting: 'hello', today: new Date() },
{ headers: { 'set-header': await commitSession(session) } },
)
useTypedLoaderData
Replacement for Remix useLoaderData
. Use the generic <typeof loader>
to
get the correct type inference.
Usage
const loaderData = useTypedLoaderData<typeof loader>()
useTypedActionData
Replacement for Remix useActionData
. Use the generic <typeof action>
to
get the correct type inference.
Usage
const actionData = useTypedActionData<typeof action>()
typeddefer
✨ New in v0.3.0
Replacement for Remix defer
helper. It also supports the optional ResponseInit
, so you can return headers, etc.
Usage
return typeddefer({
fastData: { message: 'This is fast data', today: new Date() },
slowData: new Promise(resolve => setTimeout(resolve, 2000)).then(() => {
return { message: 'This is slow data', tomorrow: new Date() }
}),
})
In your route component, use the new <TypedAwait>
component instead of the
Remix <Await>
component
export default function DeferRoute() {
const { fastData, slowData } = useTypedLoaderData<typeof loader>()
return (
<main>
<h1>Defer Route</h1>
<h2>Fast Data</h2>
<pre>{JSON.stringify(fastData, null, 2)}</pre>
<div>fastData.today is {fastData.today.toLocaleString()}</div>
<Suspense fallback={<p>Loading slow data...</p>}>
<TypedAwait
resolve={slowData}
errorElement={<p>Error loading slow data!</p>}
>
{slowData => (
<div>
<h2>Slow Data</h2>
<pre>{JSON.stringify(slowData, null, 2)}</pre>
<div>
slowData.tomorrow is {slowData.tomorrow.toLocaleString()}
</div>
</div>
)}
</TypedAwait>
</Suspense>
</main>
)
}
useTypedRouteLoaderData
Helper for useMatches
that returns the route data based on provided route id
Usage
import { loader as rootLoader } from '~/root'
const rootData = useTypedRouteLoaderData<typeof rootLoader>('root')
useTypedFetcher
Replacement for Remix useFetcher
. Use the generic <typeof loader|action>
to
get the correct type inference for the fetcher.data
property.
Usage
const fetcher = useTypedFetcher<typeof action>()
fetcher.data
redirect
In order to return a redirect
, you will need to import the redirect
function from this package, in order for the type inference to work properly.
However, you can also throw redirect()
and you can use the original redirect
function from Remix.
TypedMetaFunction
You can now get typed arguments for both data
and parentsData
from your meta
function export. Based on new feature coming to Remix
export const meta: TypedMetaFunction<typeof loader> = ({ data }) => {
return {
title: `Posts | ${data?.post.title}`,
}
}
export type LoaderType = typeof loader
export type LoaderType = typeof loader
import { type LoaderType as RootLoaderType } from '~/root'
import { type LoaderType as ParentLoaderType } from '~/routes/parent'
export const meta: TypedMetaFunction<
typeof loader,
{
'root': RootLoader
'routes/parent': ParentLoader
}
> = ({ data, parentsData }) => {
const rootData = parentsData['root']
const parentData = parentsData['routes/parent']
return {
title: `Posts | ${data?.post.title}`,
}
}
registerCustomType
✨ New in v0.2.0
remix-typed-json
support a limited number of native types in order to keep the
bundle small. However, if you need to support a custom type like Decimal
, then
use the registerCustomType
API. This way you only pay the cost of the custom
type if you use it.
type CustomTypeEntry<T> = {
type: string
is: (value: unknown) => boolean
serialize: (value: T) => string
deserialize: (value: string) => T
}
export function registerCustomType<T>(entry: CustomTypeEntry<T>)
Usage
Register the custom type in root.tsx once.
import {
typedjson,
registerCustomType,
useTypedLoaderData,
} from 'remix-typedjson'
import Decimal from 'decimal.js'
registerCustomType({
type: 'decimal',
is: (value: unknown) => value instanceof Decimal,
serialize: (value: Decimal) => value.toString(),
deserialize: (value: string) => new Decimal(value),
})
You can now serialize and deserialize the Decimal
type.
export function loader() {
const d = new Decimal('1234567890123456789012345678901234567890')
return typedjson({ greeting: 'Hello World', today: new Date(), d })
}
export default function Index() {
const data = useTypedLoaderData<typeof loader>()
return (
<>
<h2>Loader Data</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
<ul>
<li>today: {data.today.toLocaleString()}</li>
<li>
d instanceof Decimal: {data.d instanceof Decimal ? 'true' : 'false'}
</li>
<li>d: {data.d.toFixed(0)}</li>
</ul>
</>
)
}
😍 Contributors
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!