Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/ninety-jeans-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@tuyau/inertia': patch
---

Add `useRouter` hook/composable that expose a typesafe `visit` method to manually visit a route.

```tsx
import { useRouter } from '@tuyau/inertia/vue'

const router = useRouter()

router.visit({
name: 'users.posts.show',
params: { id: 1, postId: 2 }
}, { preserveState: true })
```

Also available for React.
30 changes: 30 additions & 0 deletions docs/content/docs/inertia.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ import { Link } from '@tuyau/inertia/react'
<Link route="users.posts.show" params={{ id: 1, postId: 2 }}>Go to post</Link>
```

### Manual visits

The `useRouter` hook is useful to manually visit a route in your application. It returns an object with a `visit` method that you can use to visit a route.

```tsx
import { useRouter } from '@tuyau/inertia/react'

const router = useRouter()

router.visit('users.posts.show', { id: 1, postId: 2 })
```


## Vue usage

To use the Inertia helpers in your Vue x Inertia project, you must install the Tuyau plugin :
Expand Down Expand Up @@ -85,3 +98,20 @@ import { Link } from '@tuyau/inertia/vue'
<Link route="users.posts.show" :params="{ id: 1, postId: 2 }">Go to post</Link>
</template>
```

### Manual visits

The `useRouter` hook is useful to manually visit a route in your application. It returns an object with a `visit` method that you can use to visit a route.


```vue
<script setup lang="ts">
import { useRouter } from '@tuyau/inertia/vue'

const router = useRouter()
</script>

<template>
<button @click="router.visit('users.posts.show', { id: 1, postId: 2 })">Go to post</button>
</template>
```
24 changes: 23 additions & 1 deletion packages/inertia/src/react/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { Link as InertiaLink } from '@inertiajs/react'
import type { TuyauClient, RouteName, GeneratedRoutes } from '@tuyau/client'
import { Link as InertiaLink, router as InertiaRouter } from '@inertiajs/react'

import type { ValidatedApi, LinkParams } from '../types.js'

Expand Down Expand Up @@ -50,3 +50,25 @@ export const Link: <Route extends RouteName<ValidatedApi['routes']>>(
},
// @ts-expect-error TODO: fix this
) => ReturnType<typeof LinkInner> = React.forwardRef(LinkInner) as any

export function useRouter() {
const tuyau = useTuyau()
if (!tuyau) throw new Error('You must wrap your app in a TuyauProvider')

const router = {
visit: <Route extends RouteName<ValidatedApi['routes']>>(
props: LinkProps<Route>,
options?: Parameters<typeof InertiaRouter.visit>[1],
) => {
const route = tuyau.$route(props.route, (props as any).params)
const url = tuyau.$url(props.route, { params: props.params } as any)

return InertiaRouter.visit(url, {
...options,
method: route.method[0],
})
},
}

return router
}
24 changes: 23 additions & 1 deletion packages/inertia/src/vue/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { defineComponent, h, inject } from 'vue'
import type { DefineSetupFnComponent } from 'vue'
import { Link as InertiaLink } from '@inertiajs/vue3'
import type { InertiaLinkProps } from '@inertiajs/vue3'
import type { RouteName, TuyauClient } from '@tuyau/client'
import { Link as InertiaLink, router as InertiaRouter } from '@inertiajs/vue3'

import type { ValidatedApi, LinkParams } from '../types.js'

Expand Down Expand Up @@ -54,3 +54,25 @@ export const Link: <Route extends RouteName<ValidatedApi['routes']>>(
},
{ props: ['route', 'params'] },
)

export function useRouter() {
const tuyau = inject<TuyauClient<any, any> | null>(getClientKey())
if (!tuyau) throw new Error('You must install the TuyauPlugin before using useRouter')

const router = {
visit: <Route extends RouteName<ValidatedApi['routes']>>(
props: LinkParams<Route> & Omit<InertiaLinkProps, 'href' | 'method'>,
options?: Parameters<typeof InertiaRouter.visit>[1],
) => {
const route = tuyau.$route(props.route, (props as any).params)
const url = tuyau.$url(props.route, { params: props.params } as any)

return InertiaRouter.visit(url, {
...options,
method: route.method[0],
})
},
}

return router
}
15 changes: 14 additions & 1 deletion packages/inertia/tests/react.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { test } from '@japa/runner'
import { createTuyau } from '@tuyau/client'
import type { ApiDefinition } from '@tuyau/client'

import { Link, TuyauProvider } from '../src/react/index.js'
import { Link, TuyauProvider, useRouter } from '../src/react/index.js'

const routes = [
{
Expand Down Expand Up @@ -68,4 +68,17 @@ test.group('React | Typings', () => {
const client = createTuyau({ api, baseUrl: 'http://localhost' })
TuyauProvider({ client, children: null })
})

test('useRouter typing', () => {
const router = useRouter()

router.visit({ route: 'users.index' })
router.visit({ route: 'users.comments.edit', params: ['1', '2'] })

// @ts-expect-error inexistent route
router.visit({ route: 'foo' })

// @ts-expect-error missing params
router.visit({ route: 'users.comments.edit' })
}).fails()
})
16 changes: 15 additions & 1 deletion packages/inertia/tests/vue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { test } from '@japa/runner'
import { createTuyau } from '@tuyau/client'
import type { ApiDefinition } from '@tuyau/client'

import { Link, TuyauPlugin } from '../src/vue/index.js'
import { useRouter, Link, TuyauPlugin } from '../src/vue/index.js'

const routes = [
{
Expand Down Expand Up @@ -70,4 +70,18 @@ test.group('Vue | Typings', () => {
client: createTuyau({ baseUrl: 'http://localhost', api }),
})
}).fails()

test('useRouter typing', () => {
const router = useRouter()

router.visit({ route: 'users.index' })
router.visit({ route: 'users.comments.edit', params: ['1', '2'] })
router.visit({ route: 'users.comments.edit', params: ['1', '2'] }, { preserveScroll: true })

// @ts-expect-error inexistent route
router.visit({ route: 'foo' })

// @ts-expect-error missing params
router.visit({ route: 'users.comments.edit' })
}).fails()
})
14 changes: 12 additions & 2 deletions playgrounds/inertia-react/inertia/pages/posts/create.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Head } from '@inertiajs/react'
import { Link } from '@tuyau/inertia/react'
import { Link, useRouter } from '@tuyau/inertia/react'

export default function Home() {
const router = useRouter()

return (
<>
<Head title="Create post" />

<div className="container">
<button
onClick={() =>
router.visit({
route: 'home',
})
}
>
Go to home
</button>
<p>Create post</p>
<Link route="home"></Link>
</div>
Expand Down
15 changes: 13 additions & 2 deletions playgrounds/inertia-vue/inertia/pages/posts/create.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3'
import { Link } from '@tuyau/inertia/vue'
import { useRouter } from '@tuyau/inertia/vue'

defineProps<{ version: number }>()


const router = useRouter()

const backToHome = () => {
router.visit({
route: 'home'
})
}

</script>

<template>

<Head title="Homepage" />

<div class="container">
<div class="title">AdonisJS {{ version }} x Inertia x Vue.js</div>

<Link route="home" :params="[]"> Go to Home </Link>
<button @click="backToHome">Go to home</button>
<span>
Learn more about AdonisJS and Inertia.js by visiting the
<a href="https://docs.adonisjs.com/guides/inertia">AdonisJS documentation</a>.
Expand Down