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.
MIT License
435 stars 19 forks source link

🔨 Export `UseDataFunctionReturn` & `RemixSerializedType` types #19

Closed lukebowerman closed 1 year ago

lukebowerman commented 1 year ago

Related to conversation https://github.com/kiliman/remix-typedjson/issues/18 - exports the UseDataFunctionReturn type which makes typing a loader response when consumed via useMatches and should be helpful in concert with the useRouteLoaderData function.

JoepKockelkorn commented 1 year ago

This is also relevant for usage in any custom handles, as libraries tend to provide a generics slot for the data type.

For example remix-utils exports a type for the handle called HandleStructuredData which expects a type of the data as the first argument.

Update: Or is it... 🤔 Data in a handle doesn't seem to be deserialised?

kiliman commented 1 year ago

NOTE: The UseDataFunctionReturn utility type simply returns the type of the JSON payload returned from your loader... from return typedjson(payload)

It doesn't actually convert the JSON data back to the native type (via metadata). If you look at the code for useTypedLoaderData, it does the actual conversion via deserializeRemix()

useMatches returns an array of active routes which includes the loader data. This loader data has NOT been deserialized. You will need to do that manually.

import { loader as rootLoader } from '~/root'
type RootLoaderData = UseDataFunctionReturn<typeof rootLoader>

const rootData = deserializeRemix<RootLoaderData>(useMatches()[0].data)

I plan on adding a useTypedRouteLoaderData function that will wrap all this.

const rootData = useTypedRouteLoaderData<typeof rootLoader>("root")
lukebowerman commented 1 year ago

NOTE: The UseDataFunctionReturn utility type simply returns the type of the JSON payload returned from your loader... from return typedjson(payload)

@kiliman That's a great point:

I wrote a simple hook within which I use UseDataFunctionReturn:

const deserializeMatch = <LoaderType>(data: RouteData) =>
  deserializeRemix<UseDataFunctionReturn<LoaderType>>(
    data as RemixSerializedType<UseDataFunctionReturn<LoaderType>>
  )

export const useMatchDeserialized = <LoaderType>(route: string) => {
  const match = useMatches().find((m) => m.id === route)
  if (!match) throw new Error(`Route not found for: ${route}`)
  if (!match?.data) throw new Error(`Route "${route}" did not have data`)

  const loaderData = deserializeMatch<LoaderType>(match.data)
  if (!loaderData) throw new Error(`Route "${route}" could not be deserialized`)

  return loaderData
}

I just realized look at this code that I also had to copy the RemixSerializedType into my repo over to prevent a type issue with the deserializeRemix input

declare type NonJsonTypes =
  | 'date'
  | 'set'
  | 'map'
  | 'regexp'
  | 'bigint'
  | 'undefined'
  | 'infinity'
  | '-infinity'
  | 'nan'
  | 'error'

declare type MetaType = Record<string, NonJsonTypes>

declare type RemixSerializedType<T> = {
  __obj__: T | null
  __meta__?: MetaType | null
} & (
  | T
  | {
      __meta__?: MetaType
    }
)

Would it make sense to export RemixSerializedType as well or is there a way to accomplish that type-inference without doing so?

lukebowerman commented 1 year ago

Went ahead and added RemixSerializedType export as well

kiliman commented 1 year ago

Thanks!

kiliman commented 1 year ago

This has been published a v0.1.7