Skip to content

React

The @nano_kit/react package integrates @nano_kit/store signals and Dependency Injection with React components.

Install the package using your favorite package manager:

pnpm add @nano_kit/store @nano_kit/react

The useSignal hook subscribes a React component to any accessor. The component will re-render automatically whenever the value changes.

import { signal } from '@nano_kit/store'
import { useSignal } from '@nano_kit/react'
const $count = signal(0)
export function Counter() {
const count = useSignal($count)
return (
<button onClick={() => $count(count => count + 1)}>
Count: {count}
</button>
)
}

To use the Dependency Injection system within React, wrap your application (or part of it) in InjectionContextProvider. This component initializes an InjectionContext and makes it available to child components via the React Context API.

You can provide an existing context instance or an array of providers/values.

import { provide } from '@nano_kit/store'
import { InjectionContextProvider } from '@nano_kit/react'
import { Theme$ } from './tokens'
import { App } from './App'
function Root() {
return (
<InjectionContextProvider
context={[
provide(Theme$, 'dark')
]}
>
<App />
</InjectionContextProvider>
)
}

The useInject hook retrieves a dependency from the current injection context. It throws an error if the context is missing, ensuring your dependencies are always resolved.

import { useInject } from '@nano_kit/react'
import { Theme$ } from './tokens'
export function ThemedButton() {
const theme = useInject(Theme$)
return <button className={`btn-${theme}`}>Click me</button>
}

injectHook creates a reusable hook for a given injection token. This is useful for encapsulating injection logic away from components.

import { injectHook } from '@nano_kit/react'
/* Define token */
function Theme$(): 'light' | 'dark' {
return 'light'
}
/* Create custom hook */
const useTheme = injectHook(Theme$)
export function ThemedButton() {
const theme = useTheme() // Type-safe 'light' | 'dark'
return <button className={`btn-${theme}`}>Click me</button>
}

signalHook creates a hook that subscribes to a signal returned by a getter function. Useful for wrapping signal-valued injection tokens into stable hooks.

import { signalHook, injectHook } from '@nano_kit/react'
import { Location$ } from '@nano_kit/router'
/* Creates a hook that reads and subscribes to the location signal */
const useLocation = signalHook(injectHook(Location$))
export function ActiveLink({ route }) {
const location = useLocation()
/* ... */
}

Isolate renders children with a fresh injection boundary. Use it when a subtree must not inherit dependencies from the parent context.

import { Isolate } from '@nano_kit/react'
export function Preview({ children }) {
return (
<Isolate>
{children}
</Isolate>
)
}

Dehydration is an async RSC component that dehydrates stores on the server and streams the snapshot to the client via HydrationProvider. It works on both initial page loads and RSC flight requests (client-side navigations), pushing fresh data to the client each time.

Dehydration boundaries can be nested and repeated in the same RSC tree. For example, a layout can dehydrate shared stores, while a page dehydrates page-specific stores. Boundaries in the same RSC request reuse the shared request context and skip stores that were already dehydrated.

import { inject } from '@nano_kit/store'
import { Dehydration } from '@nano_kit/react'
import { User$ } from './stores'
function Stores$() {
const { $user } = inject(User$)
/* Will execute store logic, wait for all async tasks to complete, and extract the dehydrated snapshot */
return [$user]
}
export default function Page() {
return (
<Dehydration stores={Stores$}>
<UserProfile />
</Dehydration>
)
}

If you need to run dehydration manually before rendering, use dehydrate directly and pass the snapshot via dehydrated:

import { inject } from '@nano_kit/store'
import {
Dehydration,
dehydrate,
getDehydrationContext
} from '@nano_kit/react'
import { User$ } from './stores'
function Stores$() {
const { $user } = inject(User$)
/* Will execute store logic, wait for all async tasks to complete, and extract the dehydrated snapshot */
return [$user]
}
export default async function Page() {
const dehydrated = await dehydrate(Stores$)
const context = getDehydrationContext()
/* ...You can use `context` or `dehydrated` here to implement some logic before rendering... */
return (
<Dehydration dehydrated={dehydrated}>
<UserProfile />
</Dehydration>
)
}

Props:

  • stores? — function returning an array of accessors to dehydrate
  • dehydrated? — pre-dehydrated data (skips stores dehydration if provided)
  • context? — additional providers to inject into the server context before dehydration
  • isolate? — create a new InjectionContext on the client instead of reusing the existing one

StaticDehydration is like Dehydration but performs dehydration only on the initial full-page request and skips it on RSC flight requests (client-side navigations where React fetches only the server component payload, not a full HTML page), detected via FlightDetector. In that case, during client-side navigation, stores will fetch and resolve data in the browser instead of the server.

Like Dehydration, StaticDehydration can be nested and repeated. This makes it valid to place one boundary in a layout and another boundary in a page.

import { inject } from '@nano_kit/store'
import { StaticDehydration } from '@nano_kit/react'
import { User$ } from './stores'
function Stores$() {
const { $user } = inject(User$)
/* Will execute store logic, wait for all async tasks to complete, and extract the dehydrated snapshot */
return [$user]
}
export default function Page() {
return (
<StaticDehydration stores={Stores$}>
<UserProfile />
</StaticDehydration>
)
}

Props:

  • stores? — function returning an array of accessors to dehydrate
  • dehydrated? — pre-dehydrated data (skips stores dehydration if provided)
  • context? — additional providers to inject into the server context before dehydration
  • isolate? — create a new InjectionContext on the client instead of reusing the existing one
  • flight? — override flight detection; when false, dehydration is always included

FlightDetector marks the current RSC request as a non-flight render. Place it in the root layout so StaticDehydration knows when to include dehydrated data.

import { FlightDetector } from '@nano_kit/react'
export default function RootLayout({ children }) {
return (
<html>
<body>
<FlightDetector>
{children}
</FlightDetector>
</body>
</html>
)
}

Low-level RSC helper that runs stores in the shared per-request injection context and returns the dehydrated snapshot. Use it when you need to manually prepare a snapshot before rendering instead of letting Dehydration or StaticDehydration run stores from their stores prop.

After dehydrate(Stores$) resolves, the stores have already run and populated their data. Use getDehydrationContext() when you need request-time logic based on those populated stores, such as redirecting when a loaded user is missing.

import { redirect } from 'next/navigation'
import { inject } from '@nano_kit/store'
import {
Dehydration,
dehydrate,
getDehydrationContext
} from '@nano_kit/react'
import { User$ } from './stores'
function Stores$() {
const { $user } = inject(User$)
return [$user]
}
export default async function Page() {
const dehydrated = await dehydrate(Stores$)
const context = getDehydrationContext()
const { $user } = context.get(User$)
if (!$user()) {
redirect('/login')
}
return (
<Dehydration dehydrated={dehydrated}>
<UserProfile />
</Dehydration>
)
}

HydrationProvider sets up a reactive hydration context for client-side hydration. It uses ActiveHydrator under the hood, which supports streaming — the dehydrated prop can be updated on re-renders to feed new data.

import { HydrationProvider } from '@nano_kit/react'
function App({ dehydrated }) {
return (
<HydrationProvider dehydrated={dehydrated}>
<UserProfile />
</HydrationProvider>
)
}

Props:

  • dehydrated? — dehydrated key-value pairs [string, unknown][], or a falsy value to skip hydration
  • context? — additional injection providers
  • reuse? — reuse an existing InjectionContext instead of creating a new one; true by default

StaticHydrationProvider is a simpler one-shot variant using StaticHydrator. It applies the initial dehydrated snapshot once and never updates. Use this for classic SSR where the full snapshot is available at first render.

import { StaticHydrationProvider } from '@nano_kit/react'
function App({ dehydrated }) {
return (
<StaticHydrationProvider dehydrated={dehydrated}>
<UserProfile />
</StaticHydrationProvider>
)
}

Utility to detect flight requests from HTTP headers. Returns true when the accept header does not include text/html.

import { isFlight } from '@nano_kit/react'
import { headers } from 'next/headers'
/* Works with promise-based headers */
const flightFromHeaders = await isFlight(headers())
/* Or with plain header objects */
const flightFromRequest = isFlight(req.headers)