mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
154 lines
4.6 KiB
Markdown
154 lines
4.6 KiB
Markdown
---
|
|
title: Connect to state with URL
|
|
nav: 11
|
|
---
|
|
|
|
## Connect State with URL Hash
|
|
|
|
If you want to connect state of a store to URL hash, you can create your own hash storage.
|
|
|
|
```ts
|
|
import { create } from 'zustand'
|
|
import { persist, StateStorage, createJSONStorage } from 'zustand/middleware'
|
|
|
|
const hashStorage: StateStorage = {
|
|
getItem: (key): string => {
|
|
const searchParams = new URLSearchParams(location.hash.slice(1))
|
|
const storedValue = searchParams.get(key) ?? ''
|
|
return JSON.parse(storedValue)
|
|
},
|
|
setItem: (key, newValue): void => {
|
|
const searchParams = new URLSearchParams(location.hash.slice(1))
|
|
searchParams.set(key, JSON.stringify(newValue))
|
|
location.hash = searchParams.toString()
|
|
},
|
|
removeItem: (key): void => {
|
|
const searchParams = new URLSearchParams(location.hash.slice(1))
|
|
searchParams.delete(key)
|
|
location.hash = searchParams.toString()
|
|
},
|
|
}
|
|
|
|
export const useBoundStore = create()(
|
|
persist(
|
|
(set, get) => ({
|
|
fishes: 0,
|
|
addAFish: () => set({ fishes: get().fishes + 1 }),
|
|
}),
|
|
{
|
|
name: 'food-storage', // unique name
|
|
storage: createJSONStorage(() => hashStorage),
|
|
},
|
|
),
|
|
)
|
|
```
|
|
|
|
## Persist and Connect State with URL Parameters (Example: URL Query Parameters)
|
|
|
|
There are times when you want to conditionally connect the state to the URL.
|
|
This example depicts usage of the URL query parameters
|
|
while keeping it synced with another persistence implementation, like `localstorage`.
|
|
|
|
If you want the URL params to always populate, the conditional check on `getUrlSearch()` can be removed.
|
|
|
|
The implementation below will update the URL in place, without refresh, as the relevant states change.
|
|
|
|
```ts
|
|
import { create } from 'zustand'
|
|
import { persist, StateStorage, createJSONStorage } from 'zustand/middleware'
|
|
|
|
const getUrlSearch = () => {
|
|
return window.location.search.slice(1)
|
|
}
|
|
|
|
const persistentStorage: StateStorage = {
|
|
getItem: (key): string => {
|
|
// Check URL first
|
|
if (getUrlSearch()) {
|
|
const searchParams = new URLSearchParams(getUrlSearch())
|
|
const storedValue = searchParams.get(key)
|
|
return JSON.parse(storedValue as string)
|
|
} else {
|
|
// Otherwise, we should load from localstorage or alternative storage
|
|
return JSON.parse(localStorage.getItem(key) as string)
|
|
}
|
|
},
|
|
setItem: (key, newValue): void => {
|
|
// Check if query params exist at all, can remove check if always want to set URL
|
|
if (getUrlSearch()) {
|
|
const searchParams = new URLSearchParams(getUrlSearch())
|
|
searchParams.set(key, JSON.stringify(newValue))
|
|
window.history.replaceState(null, '', `?${searchParams.toString()}`)
|
|
}
|
|
|
|
localStorage.setItem(key, JSON.stringify(newValue))
|
|
},
|
|
removeItem: (key): void => {
|
|
const searchParams = new URLSearchParams(getUrlSearch())
|
|
searchParams.delete(key)
|
|
window.location.search = searchParams.toString()
|
|
},
|
|
}
|
|
|
|
type LocalAndUrlStore = {
|
|
typesOfFish: string[]
|
|
addTypeOfFish: (fishType: string) => void
|
|
numberOfBears: number
|
|
setNumberOfBears: (newNumber: number) => void
|
|
}
|
|
|
|
const storageOptions = {
|
|
name: 'fishAndBearsStore',
|
|
storage: createJSONStorage<LocalAndUrlStore>(() => persistentStorage),
|
|
}
|
|
|
|
const useLocalAndUrlStore = create()(
|
|
persist<LocalAndUrlStore>(
|
|
(set) => ({
|
|
typesOfFish: [],
|
|
addTypeOfFish: (fishType) =>
|
|
set((state) => ({ typesOfFish: [...state.typesOfFish, fishType] })),
|
|
|
|
numberOfBears: 0,
|
|
setNumberOfBears: (numberOfBears) => set(() => ({ numberOfBears })),
|
|
}),
|
|
storageOptions,
|
|
),
|
|
)
|
|
|
|
export default useLocalAndUrlStore
|
|
```
|
|
|
|
When generating the URL from a component, you can call buildShareableUrl:
|
|
|
|
```ts
|
|
const buildURLSuffix = (params, version = 0) => {
|
|
const searchParams = new URLSearchParams()
|
|
|
|
const zustandStoreParams = {
|
|
state: {
|
|
typesOfFish: params.typesOfFish,
|
|
numberOfBears: params.numberOfBears,
|
|
},
|
|
version: version, // version is here because that is included with how Zustand sets the state
|
|
}
|
|
|
|
// The URL param key should match the name of the store, as specified as in storageOptions above
|
|
searchParams.set('fishAndBearsStore', JSON.stringify(zustandStoreParams))
|
|
return searchParams.toString()
|
|
}
|
|
|
|
export const buildShareableUrl = (params, version) => {
|
|
return `${window.location.origin}?${buildURLSuffix(params, version)}`
|
|
}
|
|
```
|
|
|
|
The generated URL would look like (here without any encoding, for readability):
|
|
|
|
`https://localhost/search?fishAndBearsStore={"state":{"typesOfFish":["tilapia","salmon"],"numberOfBears":15},"version":0}}`
|
|
|
|
### Demo
|
|
|
|
- Hash: https://stackblitz.com/edit/vitejs-vite-9vg24prg
|
|
- Query: https://stackblitz.com/edit/vitejs-vite-hyc97ynf
|