Edit this page

Store (State Management)

What is a store?

A store (aka state management) is a tool that helps you manage complex UI state.

Not properly managing UI state is one of the most common causes of buggy user interfaces. A store enables you to get even the most complex UI state logic under control.

A store works by representing state changes as atomic changes to an immutable data structure, enabling a fundamentally more robust state management.

You can use Vike with any store.

If you don't use your store on the server-side, then you can use it independently of Vike — no integration is needed. If you do, see SSR.

SSR

When do I need to use my store on the server-side?

In general, we recommend using a store only on the client-side. A store is used for highly interactive user interfaces — using it on the server-side is usually superfluous (there isn't any interactivity on the server-side).

That said, there are use cases where it can make sense, for example:

  • Dark-mode toggle: While it's client-side state, it's needed during SSR to render the correct theme (e.g. by reading the user's preference from cookies). You could manage it only on the client, but that would cause a light/dark mode flash on load if the user's setting differs from the default.
  • To-do list app using SSR: the initial to-do list (fetched with +data on the server) is expected to change — tasks can be added, removed, or updated. Therefore, it might make sense for components to access the data via the store rather than directly. In that case, you must initialize the store on the server so that components can access it during SSR.

If you use your store on the server side and have SSR enabled, you must integrate your store with SSR.

Extensions

We recommend using a Vike extension for automatically integrating your store with SSR.

💚

Contribution welcome to create Vike extensions integrating stores with SSR.

Manual integration

🧠

Instead of manually integrating your store, we generally recommend using a Vike extension instead.

👉 We recommend using this advanced capability — which can be complex — only if you have a clear reason why simpler alternatives aren't an option for you.

Feel free to reach out if you want help integrating a store.

When using a store with SSR, the initial state of the store is determined on the server side (during SSR) and then passed to the client side.

You must ensure that the store's initial state is exactly the same on both the client- and the server-side (otherwise, you'll get a hydration mismatch).

The integration can be broken down into three steps:

1. SSR

Determine the store's initial state on the server-side (during SSR) and make it available as pageContext.storeInitialState.

To achieve that, you can create the store at +onCreatePageContext.server.js and then retrieve its initial sate at +onAfterRenderHtml.js:

// pages/+onCreatePageContext.server.js
// Environment: server
 
import { createStore } from 'awesome-store'
 
export function onCreatePageContext(pageContext) {
  pageContext.store = createStore()
}
// pages/+onAfterRenderHtml.js
// Environment: server
 
export function onAfterRenderHtml(pageContext) {
  pageContext.storeInitialState = pageContext.store.getState()
}

If you use React then you may also need to use +Wrapper:

// pages/+Wrapper.jsx
// Environment: server, client
 
import { Provider } from 'awesome-store/react'
import { usePageContext } from 'vike-react/usePageContext'
 
export default function StoreProvider({ children }) {
  const pageContext = usePageContext()
  return <Provider store={pageContext.store}>{children}</Provider>
}

2. passToClient

Make pageContext.storeInitialState available on the client-side by using passToClient:

// pages/+config.js
 
export default {
  passToClient: ['storeInitialState']
}

3. Hydration

On the client-side, initialize the store with pageContext.storeInitialState upon hydration, for example at +onBeforeRenderClient:

// pages/+onBeforeRenderClient.js
// Environment: client
 
import { createStore } from 'awesome-store'
 
export function onBeforeRenderClient(pageContext) {
  if (pageContext.isHydration) {
    // Hydration. We must use the same state than on the server-side.
    pageContext.globalContext.store = createStore(pageContext.storeInitialState)
  } else {
    // Client-side navigation. Nothing to do: the store was already initialized at hydration.
    assert(pageContext.globalContext.store)
  }
}

You can also use +onCreateGlobalContext.client.js instead to create the store earlier.

See also: