inertiajs-adonisjs
inertiajs-adonisjs copied to clipboard
feat(types): add InertiaPage type helper
Based off of my enhancement request here https://github.com/eidellev/inertiajs-adonisjs/issues/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
}
Great work. Can you please update the readme?
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
}
}
🤔 I think I like the second approach better since it's less verbose. How would you use this in the client?
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
thanks. it's an interesting problem. what if we explicitly type the controller method intead?
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.
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 })
}
}