Skip to content

Commit 5fe7eb1

Browse files
authored
Support for type-hinting shared Page Props (#2636)
* Type-hint shared props * Update svelte.spec.ts
1 parent efe5193 commit 5fe7eb1

File tree

11 files changed

+195
-46
lines changed

11 files changed

+195
-46
lines changed

packages/core/src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ declare module 'axios' {
99

1010
export type DefaultInertiaConfig = {
1111
errorValueType: string
12+
sharedPageProps: PageProps
1213
}
1314
/**
1415
* Designed to allow overriding of some core types using TypeScript
@@ -20,6 +21,10 @@ export type DefaultInertiaConfig = {
2021
* declare module '@inertiajs/core' {
2122
* export interface InertiaConfig {
2223
* errorValueType: string[]
24+
* sharedPageProps: {
25+
* auth: { user: User | null }
26+
* flash: { success?: string; error?: string }
27+
* }
2328
* }
2429
* }
2530
* ```
@@ -29,6 +34,7 @@ export type InertiaConfigFor<Key extends keyof DefaultInertiaConfig> = Key exten
2934
? InertiaConfig[Key]
3035
: DefaultInertiaConfig[Key]
3136
export type ErrorValue = InertiaConfigFor<'errorValueType'>
37+
export type SharedPageProps = InertiaConfigFor<'sharedPageProps'>
3238

3339
export type Errors = Record<string, ErrorValue>
3440
export type ErrorBag = Record<string, Errors>

packages/react/src/usePage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { Page, PageProps } from '@inertiajs/core'
1+
import { Page, PageProps, SharedPageProps } from '@inertiajs/core'
22
import { useContext } from 'react'
33
import PageContext from './PageContext'
44

5-
export default function usePage<TPageProps extends PageProps = PageProps>(): Page<TPageProps> {
5+
export default function usePage<TPageProps extends PageProps = PageProps>(): Page<TPageProps & SharedPageProps> {
66
const page = useContext(PageContext)
77

88
if (!page) {
99
throw new Error('usePage must be used within the Inertia component')
1010
}
1111

12-
return page
12+
return page as Page<TPageProps & SharedPageProps>
1313
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// This component is used for checking the TypeScript implementation; there is no Playwright test depending on it.
2+
import { usePage } from '@inertiajs/react'
3+
4+
declare module '@inertiajs/core' {
5+
export interface InertiaConfig {
6+
sharedPageProps: {
7+
flash: { success?: string; error?: string }
8+
}
9+
}
10+
}
11+
12+
type PageProps = {
13+
posts: { id: number; title: string }[]
14+
}
15+
16+
export default function TypeScriptProps() {
17+
const page = usePage<PageProps>()
18+
19+
const error = page.props.flash.error
20+
const postTitles = page.props.posts.map((post) => post.title)
21+
22+
// @ts-expect-error - 'message' does not exist on flash
23+
const flashMessage = page.props.flash.message
24+
// @ts-expect-error - 'users' does not exist on page props
25+
const userNames = page.props.users.map((user) => user.name)
26+
27+
console.log({
28+
error,
29+
postTitles,
30+
flashMessage,
31+
userNames,
32+
})
33+
34+
return null
35+
}

packages/svelte/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export { default as Link } from './components/Link.svelte'
66
export { default as WhenVisible } from './components/WhenVisible.svelte'
77
export { default as createInertiaApp } from './createInertiaApp'
88
export { default as inertia } from './link'
9-
export { default as page } from './page'
9+
export { default as page, usePage } from './page'
1010
export { type ResolvedComponent } from './types'
1111
export { default as useForm, type InertiaForm, type InertiaFormProps } from './useForm'
1212
export { default as usePoll } from './usePoll'

packages/svelte/src/page.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { type Page } from '@inertiajs/core'
2-
import { writable } from 'svelte/store'
1+
import { type Page, type PageProps, type SharedPageProps } from '@inertiajs/core'
2+
import { derived, writable, type Readable } from 'svelte/store'
33

4-
type SveltePage = Omit<Page, 'props'> & {
5-
props: Page['props'] & {
4+
type SveltePage<TPageProps extends PageProps = PageProps> = Omit<Page<TPageProps & SharedPageProps>, 'props'> & {
5+
props: Page<TPageProps & SharedPageProps>['props'] & {
66
[key: string]: any
77
}
88
}
@@ -12,3 +12,7 @@ const { set, subscribe } = writable<SveltePage>()
1212
export const setPage = set
1313

1414
export default { subscribe }
15+
16+
export function usePage<TPageProps extends PageProps = PageProps>(): Readable<SveltePage<TPageProps>> {
17+
return derived({ subscribe }, ($page) => $page as SveltePage<TPageProps>)
18+
}

packages/svelte/test-app/Pages/Svelte/PropsAndPageStore.svelte

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,42 @@
11
<script lang="ts">
2-
import { inertia, page, useForm } from '@inertiajs/svelte'
2+
import { inertia, page, usePage, useForm } from '@inertiajs/svelte'
33
import { onMount } from 'svelte'
44
5+
type PageProps = {
6+
foo: string
7+
}
8+
59
export let foo
610
711
const form = useForm({ foo })
812
13+
const pageProps: PageProps = {
14+
foo: $page.props.foo,
15+
}
16+
17+
const sveltePage = usePage<PageProps>()
18+
919
console.log('[script] foo prop is', foo)
1020
console.log('[script] $page.props.foo is', $page.props.foo)
21+
console.log('[script] $sveltePage.props.foo is', $sveltePage.props.foo)
1122
1223
$: console.log('[reactive expression] foo prop is', foo)
1324
$: console.log('[reactive expression] $page.props.foo is', $page.props.foo)
25+
$: console.log('[reactive expression] $sveltePage.props.foo is', $sveltePage.props.foo)
1426
1527
onMount(() => {
1628
console.log('[onMount] foo prop is', foo)
1729
console.log('[onMount] $page.props.foo is', $page.props.foo)
30+
console.log('[onMount] $sveltePage.props.foo is', $sveltePage.props.foo)
1831
})
19-
20-
type PageProps = {
21-
foo: string
22-
}
23-
24-
const pageProps: PageProps = {
25-
foo: $page.props.foo,
26-
}
2732
</script>
2833

2934
<div>
3035
<input id="input" type="text" bind:value={$form.foo} />
3136
<p>foo prop is {foo}</p>
3237
<p>$page.props.foo is {$page.props.foo}</p>
3338
<p>pageProps.foo is {pageProps.foo}</p>
39+
<p>$sveltePage.props.foo is {$sveltePage.props.foo}</p>
3440

3541
<a href="/svelte/props-and-page-store" use:inertia={{ data: { foo: 'bar' } }}> Bar </a>
3642
<a href="/svelte/props-and-page-store" use:inertia={{ data: { foo: 'baz' } }}> Baz </a>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script lang="ts" context="module">
2+
declare module '@inertiajs/core' {
3+
export interface InertiaConfig {
4+
sharedPageProps: {
5+
flash: { success?: string; error?: string }
6+
}
7+
}
8+
}
9+
</script>
10+
11+
<script lang="ts">
12+
// This component is used for checking the TypeScript implementation; there is no Playwright test depending on it.
13+
import { usePage } from '@inertiajs/svelte'
14+
15+
type PageProps = {
16+
posts: { id: number; title: string }[]
17+
}
18+
19+
const page = usePage<PageProps>()
20+
21+
$: error = $page.props.flash.error
22+
$: postTitles = $page.props.posts.map((post) => post.title)
23+
24+
// @ts-expect-error - 'message' does not exist on flash
25+
$: flashMessage = $page.props.flash.message
26+
// @ts-expect-error - 'users' does not exist on page props
27+
$: userNames = $page.props.users.map((user) => user.name)
28+
29+
console.log({
30+
error,
31+
postTitles,
32+
flashMessage,
33+
userNames,
34+
})
35+
</script>

packages/vue3/src/app.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Page,
66
PageProps,
77
router,
8+
SharedPageProps,
89
} from '@inertiajs/core'
910
import {
1011
computed,
@@ -133,7 +134,7 @@ export const plugin: Plugin = {
133134
},
134135
}
135136

136-
export function usePage<SharedProps extends PageProps>(): Page<SharedProps> {
137+
export function usePage<TPageProps extends PageProps = PageProps>(): Page<TPageProps & SharedPageProps> {
137138
return reactive({
138139
props: computed(() => page.value?.props),
139140
url: computed(() => page.value?.url),
@@ -147,5 +148,5 @@ export function usePage<SharedProps extends PageProps>(): Page<SharedProps> {
147148
matchPropsOn: computed(() => page.value?.matchPropsOn),
148149
rememberedState: computed(() => page.value?.rememberedState),
149150
encryptHistory: computed(() => page.value?.encryptHistory),
150-
})
151+
}) as Page<TPageProps>
151152
}

packages/vue3/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createHeadManager, Page, PageHandler, router } from '@inertiajs/core'
1+
import { createHeadManager, Page, PageHandler, PageProps, router, SharedPageProps } from '@inertiajs/core'
22
import { ComponentPublicInstance } from 'vue'
33
import useForm from './useForm'
44

@@ -15,7 +15,7 @@ declare module '@inertiajs/core' {
1515
declare module 'vue' {
1616
export interface ComponentCustomProperties {
1717
$inertia: typeof router
18-
$page: Page
18+
$page: Page<PageProps & SharedPageProps>
1919
$headManager: ReturnType<typeof createHeadManager>
2020
}
2121

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script setup lang="ts">
2+
// This component is used for checking the TypeScript implementation; there is no Playwright test depending on it.
3+
import { usePage } from '@inertiajs/vue3'
4+
5+
declare module '@inertiajs/core' {
6+
export interface InertiaConfig {
7+
sharedPageProps: {
8+
flash: { success?: string; error?: string }
9+
}
10+
}
11+
}
12+
13+
type PageProps = {
14+
posts: { id: number; title: string }[]
15+
}
16+
17+
const page = usePage<PageProps>()
18+
19+
const error = page.props.flash.error
20+
const postTitles = page.props.posts.map((post) => post.title)
21+
22+
// @ts-expect-error - 'message' does not exist on flash
23+
const flashMessage = page.props.flash.message
24+
// @ts-expect-error - 'users' does not exist on page props
25+
const userNames = page.props.users.map((user) => user.name)
26+
27+
console.log({
28+
error,
29+
postTitles,
30+
flashMessage,
31+
userNames,
32+
})
33+
</script>
34+
35+
<template>
36+
{{ $page.props.flash.error }}
37+
38+
<!-- @vue-expect-error - 'message' does not exist on flash -->
39+
{{ $page.props.flash.message }}
40+
</template>

0 commit comments

Comments
 (0)