Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/kiliman/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.
https://github.com/kiliman/remix-typedjson

Last synced: 6 days ago
JSON representation

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.

Awesome Lists containing this project

README

        

# remix-typedjson

[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-)

This package is a replacement for [`superjson`](https://github.com/blitz-js/superjson) to use in your [Remix](https://remix.run/) 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

```bash
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:

```js
❌ export const loader: LoaderFunction = async ({request}) => {}
❌ export const action: ActionFunction = async ({request}) => {}

✅ export const loader = async ({request}: LoaderFunctionArgs) => {}
✅ export const action = async ({request}: ActionFunctionArgs) => {}

✅ export async function loader({request}: LoaderFunctionArgs) {}
✅ export async function action({request}: ActionFunctionArgs) {}
```

### Usage

```js
return typedjson(
{ greeting: 'hello', today: new Date() },
// ResponseInit is optional, just like the `json` helper
{ headers: { 'set-header': await commitSession(session) } },
)
```

## `useTypedLoaderData`

Replacement for Remix `useLoaderData`. Use the generic `` to
get the correct type inference.

### Usage

```js
const loaderData = useTypedLoaderData()
```

## `useTypedActionData`

Replacement for Remix `useActionData`. Use the generic `` to
get the correct type inference.

### Usage

```js
const actionData = useTypedActionData()
```

## `typeddefer`

✨ New in v0.3.0

Replacement for Remix `defer` helper. It also supports the optional `ResponseInit`, so you can return headers, etc.

### Usage

```js
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 `` component instead of the
Remix `` component

### Usage

```js

export default function DeferRoute() {
const { fastData, slowData } = useTypedLoaderData()
return (

Defer Route


Fast Data


{JSON.stringify(fastData, null, 2)}

fastData.today is {fastData.today.toLocaleString()}

Loading slow data...}>
Error loading slow data!}
>
{slowData => (

Slow Data


{JSON.stringify(slowData, null, 2)}


slowData.tomorrow is {slowData.tomorrow.toLocaleString()}


)}



)
}
```

## `useTypedRouteLoaderData`

Helper for `useMatches` that returns the route data based on provided route `id`

### Usage

```ts
import { loader as rootLoader } from '~/root'

const rootData = useTypedRouteLoaderData('root')
```

## `useTypedFetcher`

✨ Add support for `key` in v0.4.0

Replacement for Remix `useFetcher`. Use the generic `` to
get the correct type inference for the `fetcher.data` property.

### Usage

```ts
const fetcher = useTypedFetcher({ key: 'abc' })
fetcher.data // data property is fully typed
```

## `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`~~

🔥 Removed in v0.4.0 since it didn't actually work correctly. Will be replaced
with a `typedmeta` wrapper function in next release

You can now get typed arguments for both `data` and `parentsData` from your `meta`
function export. Based on [new feature coming to Remix](https://github.com/remix-run/remix/pull/4022)

```js
export const meta: TypedMetaFunction = ({ data }) => {
return {
title: `Posts | ${data?.post.title}`,
}
}
// for parentsData, you can specify a Record of typed loaders keyed by route id
// root.tsx
export type LoaderType = typeof loader
// routes/parent.tsx
export type LoaderType = typeof loader
// routes/child.tsx
import { type LoaderType as RootLoaderType } from '~/root'
import { type LoaderType as ParentLoaderType } from '~/routes/parent'

export const meta: TypedMetaFunction<
typeof loader,
// parent loader types keyed by route id
{
'root': RootLoader
'routes/parent': ParentLoader
}
> = ({ data, parentsData }) => {
// access typed parent data by route id
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.

```ts
type CustomTypeEntry = {
type: string
is: (value: unknown) => boolean
serialize: (value: T) => string
deserialize: (value: string) => T
}

export function registerCustomType(entry: CustomTypeEntry)
```

### Usage

Register the custom type in _root.tsx_ once.

```ts
// root.tsx
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.

```ts
// route.tsx
export function loader() {
const d = new Decimal('1234567890123456789012345678901234567890')
return typedjson({ greeting: 'Hello World', today: new Date(), d })
}

export default function Index() {
const data = useTypedLoaderData()

return (
<>

Loader Data


{JSON.stringify(data, null, 2)}


  • today: {data.today.toLocaleString()}


  • d instanceof Decimal: {data.d instanceof Decimal ? 'true' : 'false'}

  • d: {data.d.toFixed(0)}


>
)
}
```

## 😍 Contributors

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):



Kiliman
Kiliman

💻 📖
Kent C. Dodds
Kent C. Dodds

💻
Simon Knott
Simon Knott

💻 🐛 ⚠️
Tony Truand
Tony Truand

💻 ⚠️
Gregori Rivas
Gregori Rivas

💻
Afsah Nasir
Afsah Nasir

📖
Magnus Markling
Magnus Markling

💻


Jozsef Lazar
Jozsef Lazar

💻
Luke Bowerman
Luke Bowerman

💻
Dan Marshall
Dan Marshall

📖
Eric Allam
Eric Allam

📖

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!