docs: persist and connect state with url (#1804)

* docs: persist and connect state with url

* ran prettier

* remove line

* Apply suggestions from code review

Co-authored-by: Blazej Sewera <code@sewera.dev>

* from review comments

---------

Co-authored-by: celinecheng <celine.cheng@capitalone.com>
Co-authored-by: Blazej Sewera <code@sewera.dev>
This commit is contained in:
Celine Cheng 2023-05-17 07:52:02 -04:00 committed by GitHub
parent 56ab6db790
commit 286e4436c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,9 +1,9 @@
---
title: Connect to state with URL hash
title: Connect to state with URL
nav: 12
---
## State is connected with URL hash
## Connect State with URL Hash
If you want to connect state of a store to URL hash, you can create your own hash storage.
@ -43,6 +43,101 @@ export const useBoundStore = create(
)
```
## CodeSandbox Demo
### CodeSandbox Demo
https://codesandbox.io/s/zustand-state-with-url-hash-demo-f29b88?file=/src/store/index.ts
<https://codesandbox.io/s/zustand-state-with-url-hash-demo-f29b88?file=/src/store/index.ts>
## 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)
} else {
// Otherwise, we should load from localstorage or alternative storage
return JSON.parse(localStorage.getItem(key))
}
},
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, 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()
},
}
let localAndUrlStore = (set) => ({
typesOfFish: [],
addTypeOfFish: (fishType) =>
set((state) => ({ typesOfFish: [...state.typesOfFish, fishType] })),
numberOfBears: 0,
setNumberOfBears: (newNumber) =>
set((state) => ({ numberOfBears: newNumber })),
})
let storageOptions = {
name: 'fishAndBearsStore',
storage: persistentStorage,
}
const useLocalAndUrlStore = create(persist(localAndUrlStore, storageOptions))
export default localAndUrlStore
```
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}}`