How to create a generic binding for useLoader and loader in Remix

Hello!

I’m experimenting with Rescript inside a Remix project (remix.run), the web framework from the React router folks. Is there a more interesting, fun way to enforce the types for these to functions without duplicating the types? See playground link for some light code.

More context: code snippet pulled from Remix | Loading Data.

export let loader: LoaderFunction = () => {
  return fetch("https://api.github.com/gists");
};

export default function Gists() {
  let data = useLoaderData(); // returns the value of loader

aside: :laughing: building up the possible ways to have generated type-safety in a rescript project from database to web framework will be my happy place to json would be my happy place. easy to setup new projects, throw away, iterate on new domains.

It’s been a while since I played with remix. But around that time, there wasn’t a good way to achieve this, and everyone seemed to build their solution to that problem (have a look at the issues around this in their repo, including my own proposal for infering the types) - the repo is private as of right now, but I think it will open up this week.

But I guess it would mainly depend on the types of the bindings that you can write for remix.

Maybe not that useful but I tried to create a new playground based on your input here.

1 Like

Thanks! Yes that’s basically it, but now it’d be nice to only annotate the type once.

I’ll take this, and see if I can use a module to do that.

You could use a functor:

module Make = (
  I: {
    type t
    let loader: unit => Js.Promise.t<t>
  },
) => {
  let loader = I.loader
  @module("remix") external useLoaderData: unit => I.t = "useLoaderData"
}

type gist = {id: int}

module Gists = Make({
  type t = array<gist>

  // you can fetch, etc. whatever here, the compiler will make
  // sure you return the right type
  let loader = () => Js.Promise.resolve([{id: 1}])
})

let loader = Gists.loader

let component = () => {
  let _data = Gists.useLoaderData()
}

For each page you’d make a new module specifying the page’s data type and loader function

Here’s a playground link for the above to see the types on hover and JS output

5 Likes

Nice, great use of a functor :clap:

Does anyone have an example repo for Remix-Rescript? How do you:

  1. Create the param-route files: eg. $slug.res doesn’t seem to work for me?
  2. Get remix to ignore the .res files? I get an error: “Invalid route module file: …/index.res”

Any help would be amazing :smiley:

It’s unfortunate but not all files can be in Rescript. If you’ve seen Ryan’s nextjs Rescript starter, iirc the pages are in js/ts and imports Rescript code.

I’d write the pages in typescript, but import the necessary code from Rescript. Define your loaders, actions elsewhere then import and export them wholesale from within your remix pages.

Also, I think you’re the only person publicly working on this haha. I’ve only tried a couple times.

1 Like

Haha, someone has to push this stack, it’s got the best name: Rescript-React-Remix :smiley:

It’s a shame, I wanted to migrate a production app this week, but the way I see it, for this to work well those two points I mentioned need to be fixed. Not sure why Rescript only supports some exotic file names :thinking: And on top of that Remix needs to allow for specifying a pattern to ignore files in the routes directory, eg. ignorePath ‘**/*.res’

Bit of a late reply but I think both of these should be solvable if we are willing to accept alternatives.

We can use the routes config instead of specially named files.

  1. Get remix to ignore the .res files? I get an error: “Invalid route module file: …/index.res”

We can set "in-source": false in the ReScript config file and point the Remix compiler at the lib/ directory where the generated JavaScript files are output to.