#Typed values for useRouteLoaderData route path?

1 messages ยท Page 1 of 1 (latest)

karmic siren
#

Is there a way to validate that the route path provided to useRouteLoaderData maps to a valid path, either as part of remix directly or as a plugin/extension for remix?

i.e. so that if you used useRouteLoaderData("routes/_auth+/_layout") , typescript would be able to identify the path as invalid if there wasn't a routes/_auth+/_layout.tsx file to map to.

I am wary about just using bare strings for useRouteLoaderData, as a file rename/move could silently break part of the site and potentially have the breakage only being found at runtime. I'd far prefer issues like this to be found by the typescript language checker at compile time so they can be found easily and fixed immediately as part of the file move/rename.

karmic siren
#

remix-routes helps with route links, but doesn't currently help with the routeIds for useRouteLoaderData. I'll have to look at whether I can use what they have as a template for generating the routeIds too.

dawn sapphire
shrewd ore
#

I think it's because there's no official way of validating a route id yet

shrewd ore
sonic stump
#

Here's a script that will generate the route ids for all your routes:

import { exec } from 'node:child_process'
import * as fs from 'node:fs'

const json = await executeCommand('npx remix routes --json')
const routes = JSON.parse(json)
const ids = extractIds(routes)

fs.writeFileSync(
  'app/route-ids.d.ts',
  `const routeIds = ${JSON.stringify(ids, null, 2)} as const

export type RouteId = (typeof routeIds)[number]`,
)

async function executeCommand(command) {
  return new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        reject(error)
        return
      }
      if (stderr) {
        reject(stderr)
        return
      }
      resolve(stdout)
    })
  })
}

function extractIds(routes) {
  const traverse = node => {
    const childIds = node.children ? node.children.flatMap(traverse) : []
    return node.id ? [node.id, ...childIds] : childIds
  }

  return routes.flatMap(traverse)
}

Then you can create your own wrapper function

function useRouteLoaderDataWithId<T>(routeId: RouteId) {
  return useRouteLoaderData<T>(routeId)
}
dawn sapphire
karmic siren
shrewd ore
#

I had to resort to tomfoolery:

/** @returns {Promise<Route[]>} */
async function getRoutesJSON() {
  // Thanks, node :(
  // https://github.com/nodejs/node/issues/19218

  // So to get around this, write to a tmpfile, then read it
  const tmpfile = joinPath(
    await mkdtemp(joinPath(tmpdir(), '.remix-routes-')),
    'routes.json',
  );

  execSync(`yarn run remix routes --json > ${tmpfile}`, {
    shell: true,
    stdio: 'inherit',
  });
  const raw = await readFile(tmpfile, 'utf-8');
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
  return JSON.parse(raw);
}

๐Ÿ˜ญ

karmic siren
#

I'll take a quick look at what we get in the config - it's also expecting to only be one directory down from where appDirectory is, but if you put the file into types/remix-routes/ it'd break there too.
Edit: so will the watch script.

sonic stump
shrewd ore
#

my kingdom for a proper Remix node api

karmic siren
#

I added appDirectory handling to the PR, which has just been merged to master. So both that and the RouteId should be part of the next release ๐ŸŽ‰

shrewd ore
#

โœŠ my eternal thanks

karmic siren
#

Released as version 1.6.0