Skip to content

Instantly share code, notes, and snippets.

@cef62
Forked from tannerlinsley/README.md
Created June 12, 2023 10:19
Show Gist options
  • Select an option

  • Save cef62/f00b0d0f0827d96d1a01154110b3e7a0 to your computer and use it in GitHub Desktop.

Select an option

Save cef62/f00b0d0f0827d96d1a01154110b3e7a0 to your computer and use it in GitHub Desktop.

Revisions

  1. @tannerlinsley tannerlinsley revised this gist May 15, 2023. 1 changed file with 7 additions and 0 deletions.
    7 changes: 7 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    This middleware does a few interesting things:

    - Ensures a `url` shape in the zustand store, where we'll store URL information.
    - Assumes we will be storing our `url` state slice in the `?state` search parameter after it has been stringified and base 64 encoded.
    - On creation, decodes stores state from the `?state` search parameter into the `url` slice of our store.
    - After each state update, updates the `?state` search parameter with the new `url` state slice.
    - Sets up an event listener that listens for `popstate` and re-decodes the state from the URL into our store.
  2. @tannerlinsley tannerlinsley revised this gist May 15, 2023. No changes.
  3. @tannerlinsley tannerlinsley revised this gist May 15, 2023. No changes.
  4. @tannerlinsley tannerlinsley revised this gist May 15, 2023. No changes.
  5. @tannerlinsley tannerlinsley revised this gist May 15, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion zustand-urlMiddleWare.ts
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,7 @@
    import { create, type StateCreator } from 'zustand'
    import { immer } from 'zustand/middleware/immer'

    funciton functionalUpdate (updater, previous) {
    function functionalUpdate (updater, previous) {
    return typeof updater === 'function' ? updater(previous) : updater
    }

  6. @tannerlinsley tannerlinsley revised this gist May 15, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion zustand-urlMiddleWare.ts
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@ export type PageContext = {
    projectId?: string
    }

    export type Page = 'projects' | 'projects.project' | 'plants' | 'products'
    export type Page = 'projects' | 'projects.project'

    const urlMiddleware =
    <TState extends { url: any }>(
  7. @tannerlinsley tannerlinsley created this gist May 15, 2023.
    91 changes: 91 additions & 0 deletions zustand-urlMiddleWare.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,91 @@
    import { create, type StateCreator } from 'zustand'
    import { immer } from 'zustand/middleware/immer'

    funciton functionalUpdate (updater, previous) {
    return typeof updater === 'function' ? updater(previous) : updater
    }

    export type Store = {
    url: {
    page: Page
    context: PageContext
    }
    setPage: (page: Page, context?: PageContext) => void
    createProject: () => void
    }

    export type PageContext = {
    projectId?: string
    }

    export type Page = 'projects' | 'projects.project' | 'plants' | 'products'

    const urlMiddleware =
    <TState extends { url: any }>(
    prev: StateCreator<TState, any, any>
    ): StateCreator<TState> =>
    (set, get, api) => {
    const parseUrlState = () => {
    try {
    const search =
    new URLSearchParams(window.location.search.substring(1)).get(
    'state'
    ) || ''
    const decoded = atob(search)
    return JSON.parse(decoded)
    } catch (e) {
    return
    }
    }

    const initialState = prev(
    (...args) => {
    set(...args)
    const stringified = JSON.stringify(get().url)
    const encoded = btoa(stringified)
    history.pushState(null, '', `/?state=${encoded}`)
    },
    get,
    api
    )

    window.addEventListener('popstate', () => {
    set((state) => {
    return {
    ...state,
    url: parseUrlState(),
    }
    })
    })

    return {
    ...initialState,
    url: parseUrlState() || initialState.url,
    }
    }

    export const useStore = create(
    urlMiddleware(
    immer<Store>((set) => {
    return {
    url: {
    page: 'projects',
    context: {},
    },
    setPage: (page, context) =>
    set((draft) => {
    draft.url.page = page
    if (context) {
    draft.url.context = functionalUpdate(context, draft.url.context)
    }
    }),
    createProject: () => {
    set((draft) => {
    draft.url.page = 'projects.project'
    draft.url.context.projectId = undefined
    })
    },
    }
    })
    )
    )