From 5c1c3e27b9e5a7ee20f0b04afd0a5942343c89bb Mon Sep 17 00:00:00 2001 From: Svyatoslav Kryukov Date: Wed, 30 Oct 2024 17:23:31 +0300 Subject: [PATCH] Add v2 docs --- docs/.vitepress/config.mts | 34 ++- docs/guide/deferred-props.md | 185 ++++++++++++++++ docs/guide/history-encryption.md | 58 +++++ docs/guide/links.md | 4 + docs/guide/load-when-visible.md | 354 ++++++++++++++++++++++++++++++ docs/guide/manual-visits.md | 24 ++ docs/guide/merging-props.md | 70 ++++++ docs/guide/partial-reloads.md | 26 +-- docs/guide/polling.md | 196 +++++++++++++++++ docs/guide/prefetching.md | 342 +++++++++++++++++++++++++++++ docs/guide/progress-indicators.md | 23 ++ docs/guide/remembering-state.md | 16 +- docs/guide/the-protocol.md | 6 +- docs/guide/upgrade-guide.md | 67 ++++++ 14 files changed, 1369 insertions(+), 36 deletions(-) create mode 100644 docs/guide/deferred-props.md create mode 100644 docs/guide/history-encryption.md create mode 100644 docs/guide/load-when-visible.md create mode 100644 docs/guide/merging-props.md create mode 100644 docs/guide/polling.md create mode 100644 docs/guide/prefetching.md create mode 100644 docs/guide/upgrade-guide.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 98f22f87..dbce86ca 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -33,6 +33,7 @@ export default defineConfig({ ['meta', { property: 'og:image', content: image }], ['meta', { property: 'og:description', content: description }], ], + themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ @@ -68,6 +69,7 @@ export default defineConfig({ items: [ { text: 'Introduction', link: '/guide' }, { text: 'Demo app', link: '/guide/demo-application' }, + { text: 'Upgrade guide', link: '/guide/upgrade-guide' }, ], }, { @@ -98,28 +100,44 @@ export default defineConfig({ { text: 'Forms', link: '/guide/forms' }, { text: 'File uploads', link: '/guide/file-uploads' }, { text: 'Validation', link: '/guide/validation' }, - { text: 'Shared data', link: '/guide/shared-data' }, ], }, { - text: 'Advanced', + text: 'Data & Props', items: [ - { text: 'Events', link: '/guide/events' }, - { text: 'Testing', link: '/guide/testing' }, + { text: 'Shared data', link: '/guide/shared-data' }, { text: 'Partial reloads', link: '/guide/partial-reloads' }, - { text: 'Scroll management', link: '/guide/scroll-management' }, + { text: 'Deferred props', link: '/guide/deferred-props' }, + { text: 'Polling', link: '/guide/polling' }, + { text: 'Prefetching', link: '/guide/prefetching' }, + { text: 'Load when visible', link: '/guide/load-when-visible' }, + { text: 'Merging props', link: '/guide/merging-props' }, + { text: 'Remembering state', link: '/guide/remembering-state' }, + ], + }, + { + text: 'Security', + items: [ { text: 'Authentication', link: '/guide/authentication' }, { text: 'Authorization', link: '/guide/authorization' }, { text: 'CSRF protection', link: '/guide/csrf-protection' }, - { text: 'Error handling', link: '/guide/error-handling' }, + { text: 'History encryption', link: '/guide/history-encryption' }, + ], + }, + { + text: 'Advanced', + items: [ { text: 'Asset versioning', link: '/guide/asset-versioning' }, - { text: 'Progress indicators', link: '/guide/progress-indicators' }, - { text: 'Remembering state', link: '/guide/remembering-state' }, { text: 'Code splitting', link: '/guide/code-splitting' }, + { text: 'Error handling', link: '/guide/error-handling' }, + { text: 'Events', link: '/guide/events' }, + { text: 'Progress indicators', link: '/guide/progress-indicators' }, + { text: 'Scroll management', link: '/guide/scroll-management' }, { text: 'Server-side rendering', link: '/guide/server-side-rendering', }, + { text: 'Testing', link: '/guide/testing' }, ], }, ], diff --git a/docs/guide/deferred-props.md b/docs/guide/deferred-props.md new file mode 100644 index 00000000..9db7eb63 --- /dev/null +++ b/docs/guide/deferred-props.md @@ -0,0 +1,185 @@ +# Deferred props + +Inertia's deferred props feature allows you to defer the loading of certain page data until after the initial page render. This can be useful for improving the perceived performance of your app by allowing the initial page render to happen as quickly as possible. + +## Server side + +To defer a prop, you can use the defer method when returning your response. This method receives a callback that returns the prop data. The callback will be executed in a separate request after the initial page render. + +```ruby +class UsersController < ApplicationController + def index + render inertia: 'Users/Index', props: { + users: -> { User.all }, + roles: -> { Role.all }, + permissions: InertiaRails.defer { Permission.all }, + } + end +end +``` + +### Grouping requests + +By default, all deferred props get fetched in one request after the initial page is rendered, but you can choose to fetch data in parallel by grouping props together. + +```ruby +class UsersController < ApplicationController + def index + render inertia: 'Users/Index', props: { + users: -> { User.all }, + roles: -> { Role.all }, + permissions: InertiaRails.defer { Permission.all }, + teams: InertiaRails.defer(group: 'attributes') { Team.all }, + projects: InertiaRails.defer(group: 'attributes') { Project.all }, + tasks: InertiaRails.defer(group: 'attributes') { Task.all }, + } + end +end +``` + +In the example above, the `teams`, `projects`, and `tasks` props will be fetched in one request, while the `permissions` prop will be fetched in a separate request in parallel. Group names are arbitrary strings and can be anything you choose. + +## Client side + +On the client side, Inertia provides the `Deferred` component to help you manage deferred props. This component will automatically wait for the specified deferred props to be available before rendering its children. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Deferred } from '@inertiajs/react' + +export default () => ( + Loading...}> + + +) +``` + +== Svelte 4 + +```svelte + + + + +
Loading...
+
+ + {#each permissions as permission} + + {/each} +
+``` + +== Svelte 5 + +```svelte + + + + {#snippet fallback()} +
Loading...
+ {/snippet} + + {#each permissions as permission} + + {/each} +
+``` + +::: + +If you need to wait for multiple deferred props to become available, you can specify an array to the `data` prop. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Deferred } from '@inertiajs/react' + +export default () => ( + Loading...}> + + +) +``` + +== Svelte 4 + +```svelte + + + + +
Loading...
+
+ + +
+``` + +== Svelte 5 + +```svelte + + + + {#snippet fallback()} +
Loading...
+ {/snippet} + + +
+``` + +::: diff --git a/docs/guide/history-encryption.md b/docs/guide/history-encryption.md new file mode 100644 index 00000000..d2efe01e --- /dev/null +++ b/docs/guide/history-encryption.md @@ -0,0 +1,58 @@ +# History encryption + +Imagine a scenario where your user is authenticated, browses privileged information on your site, then logs out. If they press the back button, they can still see the privileged information that is stored in the window's history state. This is a security risk. To prevent this, Inertia.js provides a history encryption feature. + +## How it works + +When you instruct Inertia to encrypt your app's history, it uses the browser's built-in [`crypto` api](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) to encrypt the current page's data before pushing it to the history state. We store the corresponding key in the browser's session storage. When the user navigates back to a page, we decrypt the data using the key stored in the session storage. + +Once you instruct Inertia to clear your history state, we simply clear the existing key from session storage roll a new one. If we attempt to decrypt the history state with the new key, it will fail an Inertia will make a fresh request back to your server for the page data. + +> [!NOTE] +> History encryption relies on `window.crypto.subtle` which is only available in secure environments (sites with SSL enabled). + +## Opting in + +History encryption is an opt-in feature. There are several methods for enabling it: + +### Global encryption + +If you'd like to enable history encryption globally, set the `encrypt_history` config value to `true`. + +You are able to opt out of encryption on specific pages by passing `false` to the `encrypt_history` option: + +```ruby +render inertia: 'Homepage', props: {}, encrypt_history: false +``` + +### Per-request encryption + +To encrypt the history of an individual request, simply pass `true` to the `encrypt_history` option: + +```ruby +render inertia: 'Dashboard', props: {}, encrypt_history: true +``` + +### Controller-level encryption + +You can also enable history encryption for all actions in a controller by setting the `encrypt_history` config value in the controller: + +```ruby +class DashboardController < ApplicationController + inertia_config(encrypt_history: true) + + # ... +end +``` + +## Clearing history + +To clear the history state, you can pass the `clear_history` option to the `render` method: + +```ruby +render inertia: 'Dashboard', props: {}, clear_history: true +``` + +Once the response has rendered on the client, the encryption key will be rotated, rendering the previous history state unreadable. + +You can also clear history on the client site by calling `router.clearHistory()`. diff --git a/docs/guide/links.md b/docs/guide/links.md index d7d2a538..6c517f2f 100644 --- a/docs/guide/links.md +++ b/docs/guide/links.md @@ -522,3 +522,7 @@ export default () => { You can perform exact match comparisons (`===`), `startsWith()` comparisons (useful for matching a subset of pages), or even more complex comparisons using regular expressions. Using this approach, you're not limited to just setting class names. You can use this technique to conditionally render any markup on active state, such as different link text or even an SVG icon that represents the link is active. + +## Data loading attribute + +While a link is making an active request, a `data-loading` attribute is added to the link element. This allows you to style the link while it's in a loading state. The attribute is removed once the request is complete. diff --git a/docs/guide/load-when-visible.md b/docs/guide/load-when-visible.md new file mode 100644 index 00000000..a2752e97 --- /dev/null +++ b/docs/guide/load-when-visible.md @@ -0,0 +1,354 @@ +# Load when visible + +Inertia supports lazy loading data on scroll using the Intersection Observer API. It provides the `WhenVisible` component as a convenient way to load data when an element becomes visible in the viewport. + +The `WhenVisible` component accepts a `data` prop that specifies the key of the prop to load. It also accepts a `fallback` prop that specifies a component to render while the data is loading. The `WhenVisible` component should wrap the component that depends on the data. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { WhenVisible } from '@inertiajs/react' + +export default () => ( + Loading...}> + + +) +``` + +== Svelte 4 + +```svelte + + + + +
Loading...
+
+ + {#each permissions as permission} + + {/each} +
+``` + +== Svelte 5 + +```svelte + + + + {#snippet fallback()} +
Loading...
+ {/snippet} + + {#each permissions as permission} + + {/each} +
+``` + +::: + +If you'd like to load multiple props when an element becomes visible, you can provide an array to the `data` prop. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { WhenVisible } from '@inertiajs/react' + +export default () => ( + Loading...}> + + +) +``` + +== Svelte 4 + +```svelte + + + + +
Loading...
+
+ + +
+``` + +== Svelte 5 + +```svelte + + + + {#snippet fallback()} +
Loading...
+ {/snippet} + + +
+``` + +::: + +## Loading before visible + +If you'd like to start loading data before the element is visible, you can provide a value to the `buffer` prop. The buffer value is a number that represents the number of pixels before the element is visible. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { WhenVisible } from '@inertiajs/react' + +export default () => ( + Loading...}> + + +) +``` + +== Svelte 4 + +```svelte + + + + +
Loading...
+
+ + {#each permissions as permission} + + {/each} +
+``` + +== Svelte 5 + +```svelte + + + + {#snippet fallback()} +
Loading...
+ {/snippet} + + {#each permissions as permission} + + {/each} +
+``` + +::: + +In the above example, the data will start loading 500 pixels before the element is visible. + +By default, the `WhenVisible` component wraps the fallback template in a `div` element so it can ensure the element is visible in the viewport. If you want to customize the wrapper element, you can provide the `as` prop. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { WhenVisible } from '@inertiajs/react' + +export default () => ( + + + +) +``` + +== Svelte 4 + +```svelte + + + + + +``` + +== Svelte 5 + +```svelte + + + + + +``` + +::: + +## Always trigger + +By default, the `WhenVisible` component will only trigger once when the element becomes visible. If you want to always trigger the data loading when the element is visible, you can provide the `always` prop. + +This is useful when you want to load data every time the element becomes visible, such as when the element is at the end of an infinite scroll list and you want to load more data. + +Note that if the data loading request is already in flight, the component will wait until it is finished to start the next request if the element is still visible in the viewport. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { WhenVisible } from '@inertiajs/react' + +export default () => ( + + + +) +``` + +== Svelte 4 + +```svelte + + + + + +``` + +== Svelte 5 + +```svelte + + + + + +``` + +::: diff --git a/docs/guide/manual-visits.md b/docs/guide/manual-visits.md index 4d7932ca..0142bb21 100644 --- a/docs/guide/manual-visits.md +++ b/docs/guide/manual-visits.md @@ -20,6 +20,12 @@ router.visit(url, { errorBag: null, forceFormData: false, queryStringArrayFormat: 'brackets', + async: false, + showProgress: true, + fresh: false, + reset: [], + preserveUrl: false, + prefetch: false, onCancelToken: (cancelToken) => {}, onCancel: () => {}, onBefore: (visit) => {}, @@ -28,6 +34,8 @@ router.visit(url, { onSuccess: (page) => {}, onError: (errors) => {}, onFinish: (visit) => {}, + onPrefetching: () => {}, + onPrefetched: () => {}, }) ``` @@ -48,6 +56,12 @@ router.visit(url, { errorBag: null, forceFormData: false, queryStringArrayFormat: 'brackets', + async: false, + showProgress: true, + fresh: false, + reset: [], + preserveUrl: false, + prefetch: false, onCancelToken: (cancelToken) => {}, onCancel: () => {}, onBefore: (visit) => {}, @@ -56,6 +70,8 @@ router.visit(url, { onSuccess: (page) => {}, onError: (errors) => {}, onFinish: (visit) => {}, + onPrefetching: () => {}, + onPrefetched: () => {}, }) ``` @@ -76,6 +92,12 @@ router.visit(url, { errorBag: null, forceFormData: false, queryStringArrayFormat: 'brackets', + async: false, + showProgress: true, + fresh: false, + reset: [], + preserveUrl: false, + prefetch: false, onCancelToken: (cancelToken) => {}, onCancel: () => {}, onBefore: (visit) => {}, @@ -84,6 +106,8 @@ router.visit(url, { onSuccess: (page) => {}, onError: (errors) => {}, onFinish: (visit) => {}, + onPrefetching: () => {}, + onPrefetched: () => {}, }) ``` diff --git a/docs/guide/merging-props.md b/docs/guide/merging-props.md new file mode 100644 index 00000000..105e4a47 --- /dev/null +++ b/docs/guide/merging-props.md @@ -0,0 +1,70 @@ +# Merging props + +By default, Inertia overwrites props with the same name when reloading a page. However, there are instances, such as pagination or infinite scrolling, where that is not the desired behavior. In these cases, you can merge props instead of overwriting them. + +## Server side + +To specify that a prop should be merged, you can use the `merge` method on the prop value. + +```ruby +class UsersController < ApplicationController + include Pagy::Backend + + def index + _pagy, records = pagy(User.all) + + render inertia: 'Users/Index', props: { + results: InertiaRails.merge { records }, + } + end +end +``` + +On the client side, Inertia detects that this prop should be merged. If the prop returns an array, it will append the response to the current prop value. If it's an object, it will merge the response with the current prop value. + +You can also combine [deferred props](/guide/deferred-props) with mergeable props to defer the loading of the prop and ultimately mark it as mergeable once it's loaded. + +```ruby +class UsersController < ApplicationController + include Pagy::Backend + + def index + render inertia: 'Users/Index', props: { + results: InertiaRails.defer(merge: true) { pagy(User.all)[1] }, + } + end +end +``` + +## Resetting props + +On the client side, you can indicate to the server that you would like to reset the prop. This is useful when you want to clear the prop value before merging new data, such as when the user enters a new search query on a paginated list. + +The `reset` request option accepts an array of the props keys you would like to reset. + +:::tabs key:frameworks +== Vue + +```js +import { router } from '@inertiajs/vue3' + +router.reload({ reset: ['results'] }) +``` + +== React + +```js +import { router } from '@inertiajs/react' + +router.reload({ reset: ['results'] }) +``` + +== Svelte 4|Svelte 5 + +```js +import { router } from '@inertiajs/svelte' + +router.reload({ reset: ['results'] }) +``` + +::: diff --git a/docs/guide/partial-reloads.md b/docs/guide/partial-reloads.md index 1589ddc7..af0052a1 100644 --- a/docs/guide/partial-reloads.md +++ b/docs/guide/partial-reloads.md @@ -46,9 +46,6 @@ router.visit(url, { ## Except certain props -> [!WARNING] -> The `except` option is not yet supported by the Inertia Rails. - :::tabs key:frameworks == Vue @@ -182,14 +179,6 @@ class UsersController < ApplicationController def index render inertia: 'Users/Index', props: { users: InertiaRails.optional { User.all }, - - # Also works with a lambda: - # users: InertiaRails.optional(-> { User.all }), - - # Also works with a simple value, - # but this way the prop is always evaluated, - # even if not included: - # users: InertiaRails.optional(User.all), } end end @@ -204,13 +193,7 @@ On the inverse, you can use the `InertiaRails.always` method to specify that a p class UsersController < ApplicationController def index render inertia: 'Users/Index', props: { - users: InertiaRails.always(User.all), - - # Also works with block: - # users: InertiaRails.always { User.all }, - - # Also works with a lambda: - # users: InertiaRails.always(-> { User.all }), + users: InertiaRails.always { User.all }, } end end @@ -235,7 +218,12 @@ class UsersController < ApplicationController # NEVER included on standard visits # OPTIONALLY included on partial reloads # ONLY evaluated when needed - users: InertiaRails.lazy { User.all }, + users: InertiaRails.optional { User.all }, + + # ALWAYS included on standard visits + # ALWAYS included on partial reloads + # ALWAYS evaluated + users: InertiaRails.always { User.all }, } end end diff --git a/docs/guide/polling.md b/docs/guide/polling.md new file mode 100644 index 00000000..ec035ae7 --- /dev/null +++ b/docs/guide/polling.md @@ -0,0 +1,196 @@ +# Polling + +## Poll helper + +Polling your server for new information on the current page is common, so Inertia provides a poll helper designed to help reduce the amount of boilerplate code. In addition, the poll helper will automatically stop polling when the page is unmounted. + +The only required argument is the polling interval in milliseconds. + +:::tabs key:frameworks +== Vue + +```js +import { usePoll } from '@inertiajs/vue3' +usePoll(2000) +``` + +== React + +```js +import { usePoll } from '@inertiajs/react' +usePoll(2000) +``` + +== Svelte 4|Svelte 5 + +```js +import { usePoll } from '@inertiajs/svelte' +usePoll(2000) +``` + +::: + +If you need to pass additional request options to the poll helper, you can pass any of the `router.reload` options as the second parameter. + +:::tabs key:frameworks +== Vue + +```js +import { usePoll } from '@inertiajs/vue3' + +usePoll(2000, { + onStart() { + console.log('Polling request started') + }, + onFinish() { + console.log('Polling request finished') + }, +}) +``` + +== React + +```js +import { usePoll } from '@inertiajs/react' + +usePoll(2000, { + onStart() { + console.log('Polling request started') + }, + onFinish() { + console.log('Polling request finished') + }, +}) +``` + +== Svelte 4|Svelte 5 + +```js +import { usePoll } from '@inertiajs/svelte' + +usePoll(2000, { + onStart() { + console.log('Polling request started') + }, + onFinish() { + console.log('Polling request finished') + }, +}) +``` + +::: + +If you'd like more control over the polling behavior, the poll helper provides `stop` and `start` methods that allow you to manually start and stop polling. You can pass the `autoStart: false` option to the poll helper to prevent it from automatically starting polling when the component is mounted. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { usePoll } from '@inertiajs/react' + +export default () => { + const { start, stop } = usePoll( + 2000, + {}, + { + autoStart: false, + }, + ) + return ( +
+ + +
+ ) +} +``` + +== Svelte 4|Svelte 5 + +```svelte + + + + +``` + +::: + +## Throttling + +By default, the poll helper will throttle requests by 90% when the browser tab is in the background. If you'd like to disable this behavior, you can pass the `keepAlive` option to the poll helper. + +:::tabs key:frameworks +== Vue + +```js +import { usePoll } from '@inertiajs/vue3' + +usePoll( + 2000, + {}, + { + keepAlive: true, + }, +) +``` + +== React + +```js +import { usePoll } from '@inertiajs/react' + +usePoll( + 2000, + {}, + { + keepAlive: true, + }, +) +``` + +== Svelte 4|Svelte 5 + +```js +import { usePoll } from '@inertiajs/svelte' + +usePoll( + 2000, + {}, + { + keepAlive: true, + }, +) +``` + +::: diff --git a/docs/guide/prefetching.md b/docs/guide/prefetching.md new file mode 100644 index 00000000..0a227e6e --- /dev/null +++ b/docs/guide/prefetching.md @@ -0,0 +1,342 @@ +# Prefetching + +Inertia supports prefetching data for pages that are likely to be visited next. This can be useful for improving the perceived performance of your app by allowing the data to be fetched in the background while the user is still interacting with the current page. + +## Link prefetching + +To prefetch data for a page, you can use the `prefetch` method on the Inertia link component. By default, Inertia will prefetch the data for the page when the user hovers over the link after more than 75ms. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Link } from '@inertiajs/react' + +export default () => ( + + Users + +) +``` + +== Svelte 4|Svelte 5 + +```svelte + + +Users +``` + +::: + +By default, data is cached for 30 seconds before being evicted. You can customize this behavior by passing a `cacheFor` prop to the `Link` component. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Link } from '@inertiajs/react' + +export default () => ( + <> + + Users + + + Users + + + Users + + +) +``` + +== Svelte 4|Svelte 5 + +```svelte + + +Users +Users +Users +``` + +::: + +You can also start prefetching on `mousedown` by passing the `click` value to the `prefetch` prop. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Link } from '@inertiajs/react' + +export default () => ( + + Users + +) +``` + +== Svelte 4|Svelte 5 + +```svelte + + +Users +``` + +::: + +If you're confident that the user will visit a page next, you can prefetch the data on mount as well. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Link } from '@inertiajs/react' + +export default () => ( + + Users + +) +``` + +== Svelte 4|Svelte 5 + +```svelte + + +Users +``` + +::: + +You can also combine strategies by passing an array of values to the `prefetch` prop. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Link } from '@inertiajs/react' + +export default () => ( + + Users + +) +``` + +== Svelte 4|Svelte 5 + +```svelte + + +Users +``` + +::: + +## Programmatic prefetching + +You can also prefetch data programmatically using `router.prefetch`. The signature is identical to `router.visit` with the exception of a third argument that allows you to specify prefetch options. + +When the `cacheFor` option is not specified, it defaults to 30 seconds. + +```js +router.prefetch('/users', { method: 'get', data: { page: 2 } }) + +router.prefetch( + '/users', + { method: 'get', data: { page: 2 } }, + { cacheFor: '1m' }, +) +``` + +To make this even easier, Inertia offers a prefetch helper. This helper provides some additional insight into the request, such as the last updated timestamp and if the request is currently prefetching. + +:::tabs key:frameworks +== Vue + +```js +import { usePrefetch } from '@inertiajs/vue3' + +const { lastUpdatedAt, isPrefetching, isPrefetched } = usePrefetch( + '/users', + { method: 'get', data: { page: 2 } }, + { cacheFor: '1m' }, +) +``` + +== React + +```js +import { usePrefetch } from '@inertiajs/react' + +const { lastUpdatedAt, isPrefetching, isPrefetched } = usePrefetch( + '/users', + { method: 'get', data: { page: 2 } }, + { cacheFor: '1m' }, +) +``` + +== Svelte 4|Svelte 5 + +```js +import { usePrefetch } from '@inertiajs/svelte' + +const { lastUpdatedAt, isPrefetching, isPrefetched } = usePrefetch( + '/users', + { method: 'get', data: { page: 2 } }, + { cacheFor: '1m' }, +) +``` + +::: + +## Flushing prefetch cache + +You can flush the prefetch cache by calling `router.flushAll`. This will remove all cached data for all pages. + +If you want to flush the cache for a specific page, you can pass the page URL and options to the `router.flush` method. + +Furthermore, if you are using the prefetch helper, it will return a `flush` method for you to use for that specific page. + +```js +// Flush all prefetch cache +router.flushAll() + +// Flush cache for a specific page +router.flush('/users', { method: 'get', data: { page: 2 } }) + +// Flush cache for a specific page +const { flush } = usePrefetch('/users', { method: 'get', data: { page: 2 } }) +flush() +``` + +## Stale while revalidate + +By default, Inertia will fetch a fresh copy of the data when the user visits the page if the cached data is older than the cache duration. You can customize this behavior by passing a tuple to the `cacheFor` prop. + +The first value in the array represents the number of seconds the cache is considered fresh, while the second value defines how long it can be served as stale data before fetching data from the server is necessary. + +:::tabs key:frameworks +== Vue + +```vue + + + +``` + +== React + +```jsx +import { Link } from '@inertiajs/react' + +export default () => ( + + Users + +) +``` + +== Svelte 4|Svelte 5 + +```svelte + + + + Users + +``` + +::: + +### How it works + +If a request is made within the fresh period (before the first value), the cache is returned immediately without making a request to the server. + +If a request is made during the stale period (between the two values), the stale value is served to the user, and a request is made in the background to refresh the cached value. Once the value is returned, the data is merged into the page so the user has the most recent data. + +If a request is made after the second value, the cache is considered expired, and the value is fetched from the sever as a regular request. diff --git a/docs/guide/progress-indicators.md b/docs/guide/progress-indicators.md index 3ca366f9..e918ae89 100644 --- a/docs/guide/progress-indicators.md +++ b/docs/guide/progress-indicators.md @@ -294,3 +294,26 @@ router.on('finish', (event) => { ``` ::: + +## Visit Options + +In addition to these configurations, Inertia.js provides two visit options to control the loading indicator on a per-request basis: `showProgress` and `async`. These options offer greater control over how Inertia.js handles asynchronous requests and manages progress indicators. + +### `showProgress` + +The `showProgress` option provides fine-grained control over the visibility of the loading indicator during requests. + +```js +router.get('/settings', {}, { showProgress: false }) +``` + +### `async` + +The `async` option allows you to perform asynchronous requests without displaying the default progress indicator. It can be used in combination with the `showProgress` option. + +```js +// Disable the progress indicator +router.get('/settings', {}, { async: true }) +// Enable the progress indicator with async requests +router.get('/settings', {}, { async: true, showProgress: true }) +``` diff --git a/docs/guide/remembering-state.md b/docs/guide/remembering-state.md index 9c209132..1d171049 100644 --- a/docs/guide/remembering-state.md +++ b/docs/guide/remembering-state.md @@ -8,7 +8,7 @@ To mitigate this issue, you can tell Inertia which local component state to save ## Saving local state -To save local component state to the history state, use the `remember` feature to tell Inertia which data it should remember. +To save local component state to the history state, use the "useRemember" hook to tell Inertia which data it should remember. :::tabs key:frameworks == Vue @@ -41,9 +41,9 @@ export default function Profile() { == Svelte 4|Svelte 5 ```js -import { remember } from '@inertiajs/svelte' +import { useRemember } from '@inertiajs/svelte' -const form = remember({ +const form = useRemember({ first_name: null, last_name: null, }) @@ -93,9 +93,9 @@ export default function Profile() { == Svelte 4|Svelte 5 ```js -import { page, remember } from '@inertiajs/svelte' +import { page, useRemember } from '@inertiajs/svelte' -let form = remember( +let form = useRemember( { first_name: null, last_name: null, @@ -144,9 +144,9 @@ export default function Profile() { == Svelte 4|Svelte 5 ```js -import { page, remember } from '@inertiajs/svelte' +import { page, useRemember } from '@inertiajs/svelte' -let form = remember( +let form = useRemember( { first_name: $page.props.user.first_name, last_name: $page.props.user.last_name, @@ -193,7 +193,7 @@ const form = useForm(`EditUser:${user.id}`, data) ## Manually saving state -The `remember` property in Vue 2, and the `useRemember` hook in Vue 3, React, and Svelte all watch for data changes and automatically save those changes to the history state. Then, Inertia will restore the data on page load. +The `useRemember` hook watches for data changes and automatically saves them to the history state. When navigating back to the page, Inertia will restore this data. However, it's also possible to manage this manually using the underlying `remember()` and `restore()` methods in Inertia. diff --git a/docs/guide/the-protocol.md b/docs/guide/the-protocol.md index 1145d216..a2bc0061 100644 --- a/docs/guide/the-protocol.md +++ b/docs/guide/the-protocol.md @@ -66,7 +66,9 @@ X-Inertia: true } }, "url": "/events/80", - "version": "c32b8e4965f418ad16eaebba1d4e960f" + "version": "c32b8e4965f418ad16eaebba1d4e960f", + "encryptHistory": true, + "clearHistory": false } ``` @@ -78,6 +80,8 @@ Inertia shares data between the server and client via a page object. This object 2. `props`: The page props (data). 3. `url`: The page URL. 4. `version`: The current asset version. +5. `encryptHistory`: Whether or not to encrypt the current page's history state. +6. `clearHistory`: Whether or not to clear any encrypted history state. On standard full page visits, the page object is JSON encoded into the `data-page` attribute in the root `
`. On Inertia visits, the page object is returned as the JSON payload. diff --git a/docs/guide/upgrade-guide.md b/docs/guide/upgrade-guide.md new file mode 100644 index 00000000..f81b5b30 --- /dev/null +++ b/docs/guide/upgrade-guide.md @@ -0,0 +1,67 @@ +# Upgrade guide for v2.0 + +> [!NOTE] +> Inertia.js v2.0 is still in beta and these docs are a work-in-progress. Please report bugs on +> https://github.com/inertiajs/inertia and https://github.com/inertiajs/inertia-rails + +## What's new + +Inertia.js v2.0 is a huge step forward for Inertia! The core library has been completely rewritten to architecturally support asynchronous requests, enabling a whole set of new features, including: + +- [Polling](/guide/polling) +- [Prefetching](/guide/prefetching) +- [Deferred props](/guide/deferred-props) +- [Infinite scrolling](/guide/merging-props) +- [Lazy loading data on scroll](/guide/load-when-visible) + +Additionally, for security sensitive projects, Inertia now offers a [history encryption API](/guide/history-encryption), allowing you to clear page data from history state when logging out of an application. + +## Upgrade dependencies + +To upgrade to the Inertia.js v2.0 beta, first use npm to install the client-side adapter of your choice: + +:::tabs key:frameworks +== Vue + +```vue +npm install @inertiajs/vue3@next +``` + +== React + +```jsx +npm install @inertiajs/react@next +``` + +== Svelte 4|Svelte 5 + +```svelte +npm install @inertiajs/svelte@next +``` + +::: + +Next, upgrade the `inertia-rails` gem to use the `v2` dev branch: + +```ruby +gem 'inertia_rails', github: 'inertiajs/inertia-rails', branch: 'v2' +``` + +## Breaking changes + +While a significant release, Inertia.js v2.0 doesn't introduce many breaking changes. Here's a list of all the breaking changes: + +### Dropped Vue 2 support + +The Vue 2 adapter has been removed. Vue 2 reached End of Life on December 3, 2023, so this felt like it was time. + +### Svelte adapter + +- Dropped support for Svelte 3 as it reached End of Life on June 20, 2023. +- The `remember` helper has been renamed to `useRemember` to be consistent with other helpers. +- Updated `setup` callback in `inertia.js`. You need to pass `props` when initializing the `App` component. [See the updated guide](/guide/client-side-setup#initialize-the-inertia-app) +- The `setup` callback is now required in `ssr.js`. [See the updated guide](/guide/server-side-rendering#add-server-entry-point) + +### Partial reloads are now async + +Previously partial reloads in Inertia were synchronous, just like all Inertia requests. In v2.0, partial reloads are now asynchronous. Generally this is desirable, but if you were relying on these requests being synchronous, you may need to adjust your code.