Skip to content

Instantly share code, notes, and snippets.

@tannerlinsley
Last active June 12, 2023 10:19
Show Gist options
  • Save tannerlinsley/0ffe9dbf87a6e1dcb88e529a1941c7e5 to your computer and use it in GitHub Desktop.
Save tannerlinsley/0ffe9dbf87a6e1dcb88e529a1941c7e5 to your computer and use it in GitHub Desktop.

Revisions

  1. 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 revised this gist May 15, 2023. No changes.
  3. tannerlinsley revised this gist May 15, 2023. No changes.
  4. tannerlinsley revised this gist May 15, 2023. No changes.
  5. 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 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 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
    })
    },
    }
    })
    )
    )