Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(types): add InertiaPage type helper #110

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

codytooker
Copy link

Based off of my enhancement request here #109

The Problem

When using Inertia with Typescript there is currently no type safety when it comes to the frontend framework page that is rendered.

You can do something like this but we are sort of just faking the type safety here because things can get out of sync

interface FooPageProps {
   bar: string
}

const FooPage = ({ bar }: FooPageProps) => {
    return <div>{bar}</div>
}

Simply by adding some generics to the Inertia.render function we can then create a helper type that can pull the ResponseProps out for us.

So now we can do the following

import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { InertiaPage } from '@ioc:EidelLev/Inertia'

export default class FooController {
  public async index({ inertia }: HttpContextContract) {
 
    return inertia.render('Foo', {
      bar: "FooBar",
    })
  }
}

export type FooIndexProps = InertiaPage<FooController['index']>
import type FooIndexProps from 'App/Controllers/FooController'

const FooPage = ({ bar }: FooIndexProps) => {
    return <div>{bar}</div>
}

FooIndexProps now equals

type FooIndexProps = {
    bar: string
}

@eidellev
Copy link
Owner

eidellev commented Mar 7, 2023

Great work. Can you please update the readme?

@codytooker
Copy link
Author

Added a readme. Is the name of the Type good in your opinion? With this approach there is the need to make a new type and export it for every controller method. If this is a resource method, It could end up being annoying to do so. I think I can take it one step further and allow for something like

type FooControllerProps = InertiaController<FooController>

which would produce something like

type FooControllerProps = {
    index: {
         foos: string[]
    }
    show: {
          foo: string
     }
}

@eidellev
Copy link
Owner

eidellev commented Mar 7, 2023

🤔 I think I like the second approach better since it's less verbose.
How would you use this in the client?

@codytooker
Copy link
Author

codytooker commented Mar 7, 2023

On the client you would use it like so

import type FooControllerProps from 'App/Controllers/FooController'

const FooPage = ({ bar }: FooControllerProps['show']) => {
    return <div>{bar}</div>
}

The type to get this to work is

type InertiaController<T> = {
  [K in keyof T]: T[K] extends AdonisControllerMethod
    ? InertiaPage<T[K]>
    : never
}

This works, but after a little more testing, InertiaPage does not work if for instance there are multiple returns in a method, as it cannot figure out which return will happen. I can do a little more testing to see if I can come up with a fix for it.

As an example of what I am talking about. If we have the folllowing

export default class FooController {
  public async index({ inertia, request }: HttpContextContract) {
   if(request.params().noInertia) {
      return {
        test: 'test',
      }
    }
    return inertia.render('Foo', {
      bar: "FooBar",
    })
  }
}

InertiaPage<FooController['index']> // = unknown

@eidellev
Copy link
Owner

eidellev commented Mar 7, 2023

thanks. it's an interesting problem.
what if we explicitly type the controller method intead?

@codytooker
Copy link
Author

I think there is ways around it. I'll have more time to dig in over the next few days and see what kind of solution I can come up with.

@mle-moni
Copy link

Hello, I'm a bit late, but I usually use this for type safety:

// app/utils/inertiaRender.ts
import { ComponentProps } from 'react'

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

export const inertiaRender = async <T extends (props: any) => JSX.Element>(
  inertia: HttpContextContract['inertia'],
  component: T,
  props: ComponentProps<T>
) => {
  return inertia.render(component.name, props)
}
// resources/js/Pages/Home.tsx
const Home = ({ email }: { email: string }) => <h1>Hello {email}</h1>
// app/Controllers/Http/home/HomeController.ts
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Home from 'Resources/js/Pages/Home'
import { inertiaRender } from 'App/utils/inertiaRender'

export default class HomeController {
  public async index({ inertia, auth }: HttpContextContract) {
    const user = auth.user!

    return inertiaRender(inertia, Home, { email: user.email })
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants