The Transfer State integration allows you to manage the state of your application isolated by request and transferred from the server to the client.
With this, instead of having to use Astro.locals
for isolation and pass every value to the UI frameworks through properties or custom scripts, you can use the setState
and getState
functions to share the state between Astro and any UI framework component (Svelte, SolidJS, React…).
Setup
For setting up the state management with request isolation and transfer state, include the integration in your Astro project:
- Install the library and its dependencies using your preferred package manager:
npm i -D @astro-tools/transfer-state astro-integration-kit
- Add the integration to your project configuration:
astro.config.mjs import { defineConfig } from 'astro/config';import { transferState } from '@astro-tools/transfer-state';export default defineConfig({integrations: [transferState()],});
Use
For using the state management, just use setState
and getState
methods:
setState
Use setState
from the virtual module @astro-tools:transfer-state
with a key to save the value (JSON-like) into the state. Keys can be removed using null
as value.
import { setState } from '@astro-tools:transfer-state';
import type { MyData } from './my-data';
const myData: MyData = { name: 'example' };setState('my-data', myData);
If, for any reason, transferring the state should be avoided, then use the transfer: boolean
parameter within the key to mark the key as not transferrable:
import { setState } from '@astro-tools:transfer-state';
import type { MyData } from './my-data';
const myData: MyData = { name: 'example' };setState({ name: 'my-private-data', transfer: false }, myData);
getState
Use getState
from the virtual module @astro-tools:transfer-state
to get the current value of a key. When there is no value, null
will be returned.
Passing a key with transfer: boolean
parameter will be ignored.
import { getState } from '@astro-tools:transfer-state';
import type { MyData } from './my-data';
const myData = getState<MyData>('my-data');
Debugging
This integration adds a new option into the Astro Dev Toolbar which allows to easily check the transferred state from the server to the client. Find the icon highlighed in the image below and click it to toggle the state viewer:
Examples
The state can be used to render server-side UI framework components, like the one in this example.
The Example.svelte
is rendered in server-side using the uuid
state and transferred to the client. The hydration process keeps the value as it comes from server:
---import { setState } from '@astro-tools:transfer-state';
import ExampleComponent from './Example.svelte';
interface Props { id: string;}
const { id } = Astro.props;
setState('uuid', 'fc379108-c24e-47f5-b119-45db86e0e94a');---<button id="trigger">Click me to hydrate!</button><hr /><ExampleComponent {id} client:on="click #trigger" />
<script lang="ts">import { onMount } from 'svelte';
import Output from '@/libs/examples/Output.svelte';import { notifyHydration } from '@/libs/examples/hydration';
import { getState } from '@astro-tools:transfer-state';
export let id: string;
let hydrated = false;
onMount(() => { hydrated = true; notifyHydration(id);});</script>
<Output hydrated={hydrated} text={getState('uuid')} />
<script lang="ts">export let text: string;export let hydrated = false;</script>
<div class="output" class:output--hydrated={hydrated}>{text}</div>
<style lang="scss"> @use './Output.scss';</style>
Also, the state value can be recovered at any moment, for example, after hydration or any logic you want:
---import { setState } from '@astro-tools:transfer-state';
import ExampleComponent from './Example.svelte';
interface Props { id: string;}
const { id } = Astro.props;
setState('uuid-after-hydration', 'bed6fb83-0dd9-4566-a675-55052529f18e');---<button id="after-hydration-trigger">Click me to hydrate!</button><hr /><ExampleComponent {id} client:on="click #after-hydration-trigger" />
<script lang="ts">import { onMount } from 'svelte';
import Output from '@/libs/examples/Output.svelte';import { notifyHydration } from '@/libs/examples/hydration';
import { getState } from '@astro-tools:transfer-state';
export let id: string;
let hydrated = false;let text = 'Waiting for hydration...';
onMount(() => { text = getState('uuid-after-hydration'); hydrated = true; notifyHydration(id);});</script>
<Output hydrated={hydrated} text={text} />
<script lang="ts">export let text: string;export let hydrated = false;</script>
<div class="output" class:output--hydrated={hydrated}>{text}</div>
<style lang="scss"> @use './Output.scss';</style>
Finally, the state can be used in server-side without transfer it to the client. It could make sense for some use cases in which request isolation state is required but the state is private and removed from the client bundle using import.meta.env.SSR
:
---import { setState } from '@astro-tools:transfer-state';
import ExampleComponent from './Example.svelte';
interface Props { id: string;}
const { id } = Astro.props;
setState( { name: 'uuid-without-transfer', transfer: false }, 'fc379108-c24e-47f5-b119-45db86e0e94a',);---<button id="trigger-without-transfer">Click me to hydrate!</button><hr /><ExampleComponent {id} client:on="click #trigger-without-transfer" />
<script lang="ts">import { onMount } from 'svelte';
import Output from '@/libs/examples/Output.svelte';import { notifyHydration } from '@/libs/examples/hydration';
import { getState } from '@astro-tools:transfer-state';
export let id: string;
let hydrated = false;
onMount(() => { hydrated = true; notifyHydration(id);});</script>
<Output hydrated={hydrated} text={getState('uuid-without-transfer') || 'Missing UUID!'} />
<script lang="ts">export let text: string;export let hydrated = false;</script>
<div class="output" class:output--hydrated={hydrated}>{text}</div>
<style lang="scss"> @use './Output.scss';</style>