Перейти к содержанию

Подключение к состоянию с помощью URL

Связь состояния с хэшем URL

Если вы хотите связать состояние хранилища с хэшем URL, вы можете создать собственное хэш-хранилище.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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),
        }
    )
);

CodeSandbox Demo

https://codesandbox.io/s/zustand-state-with-url-hash-demo-f29b88?file=/src/store/index.ts

Сохранение и подключение состояния с помощью параметров URL (пример: параметры запроса URL)

Бывают случаи, когда необходимо условно связать состояние с URL. В этом примере показано использование параметров запроса URL при сохранении синхронизации с другой реализацией персистентности, например localstorage.

Если вы хотите, чтобы параметры URL всегда заполнялись, условную проверку на getUrlSearch() можно убрать.

Реализация, представленная ниже, будет обновлять URL на месте, без обновления, по мере изменения соответствующих состояний.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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;

При генерации URL из компонента вы можете вызвать buildShareableUrl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
    )}`;
};

Сгенерированный URL будет выглядеть так (здесь без кодировки, для удобства чтения):

https://localhost/search?fishAndBearsStore={"state":{"typesOfFish":["tilapia","salmon"],"numberOfBears":15},"version":0}}

Источник — https://docs.pmnd.rs/zustand/guides/connect-to-state-with-url-hash

Комментарии