zustand-querystring 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -29
- package/lib/middleware.d.ts +1 -0
- package/lib/middleware.js +46 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,57 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
A zustand middleware that stores state in the querystring.
|
|
4
4
|
|
|
5
|
+
Try on [StackBlitz](https://stackblitz.com/github/nitedani/zustand-querystring/tree/main/examples/react) (You need to click "Open in New Tab")
|
|
6
|
+
|
|
5
7
|
Quickstart:
|
|
6
8
|
```ts
|
|
7
9
|
import create from "zustand";
|
|
8
|
-
import { immer } from "zustand/middleware/immer";
|
|
9
10
|
import { querystring } from "zustand-querystring";
|
|
10
11
|
|
|
11
12
|
interface Store {
|
|
12
13
|
count: number;
|
|
13
|
-
incrementCount: () => void;
|
|
14
|
-
|
|
15
14
|
ticks: number;
|
|
16
|
-
incrementTicks: () => void;
|
|
17
|
-
|
|
18
15
|
someNestedState: {
|
|
19
16
|
nestedCount: number;
|
|
20
|
-
incrementNestedCount: () => void;
|
|
21
|
-
|
|
22
17
|
hello: string;
|
|
23
|
-
setHello: (hello: string) => void;
|
|
24
18
|
};
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
export const useStore = create<Store>()(
|
|
28
22
|
querystring(
|
|
29
|
-
|
|
23
|
+
(set, get) => ({
|
|
30
24
|
count: 0,
|
|
31
|
-
incrementCount: () =>
|
|
32
|
-
set((state) => {
|
|
33
|
-
state.count += 1;
|
|
34
|
-
}),
|
|
35
|
-
|
|
36
25
|
ticks: 0,
|
|
37
|
-
incrementTicks: () =>
|
|
38
|
-
set((state) => {
|
|
39
|
-
state.ticks += 1;
|
|
40
|
-
}),
|
|
41
|
-
|
|
42
26
|
someNestedState: {
|
|
43
27
|
nestedCount: 0,
|
|
44
|
-
incrementNestedCount: () =>
|
|
45
|
-
set((state) => {
|
|
46
|
-
state.someNestedState.nestedCount += 1;
|
|
47
|
-
}),
|
|
48
|
-
|
|
49
28
|
hello: "Hello",
|
|
50
|
-
setHello: (hello: string) =>
|
|
51
|
-
set((state) => {
|
|
52
|
-
state.someNestedState.hello = hello;
|
|
53
|
-
}),
|
|
54
29
|
},
|
|
55
|
-
})
|
|
30
|
+
}),
|
|
56
31
|
{
|
|
57
32
|
// select controls what part of the state is synced with the query string
|
|
58
33
|
// pathname is the current route (e.g. /about or /)
|
package/lib/middleware.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ type DeepSelect<T> = T extends object ? {
|
|
|
5
5
|
export interface QueryStringOptions<T> {
|
|
6
6
|
url?: string;
|
|
7
7
|
select?: (pathname: string) => DeepSelect<T>;
|
|
8
|
+
key?: string;
|
|
8
9
|
}
|
|
9
10
|
type QueryString = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, Mps, Mcs>, options?: QueryStringOptions<T>) => StateCreator<T, Mps, Mcs>;
|
|
10
11
|
export declare const querystring: QueryString;
|
package/lib/middleware.js
CHANGED
|
@@ -24,6 +24,10 @@ const compact = (newState, initialState) => {
|
|
|
24
24
|
return output;
|
|
25
25
|
};
|
|
26
26
|
const translateSelectionToState = (selection, state) => Object.keys(selection).reduce((acc, key) => {
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
if (!(key in state)) {
|
|
29
|
+
return acc;
|
|
30
|
+
}
|
|
27
31
|
const value = selection[key];
|
|
28
32
|
if (typeof value === 'boolean') {
|
|
29
33
|
if (value) {
|
|
@@ -38,28 +42,39 @@ const translateSelectionToState = (selection, state) => Object.keys(selection).r
|
|
|
38
42
|
const queryStringImpl = (fn, options) => (set, get, api) => {
|
|
39
43
|
const defaultedOptions = {
|
|
40
44
|
partialize: state => state,
|
|
45
|
+
key: '$',
|
|
41
46
|
...options,
|
|
42
47
|
};
|
|
43
48
|
const url = defaultedOptions.url;
|
|
44
49
|
const initialState = get() ?? fn(set, get, api);
|
|
45
|
-
const getSelectedState =
|
|
50
|
+
const getSelectedState = state => {
|
|
46
51
|
if (defaultedOptions.select) {
|
|
47
52
|
const selection = defaultedOptions.select(window.location.pathname);
|
|
48
53
|
// translate the selection to state
|
|
49
|
-
const selectedState = translateSelectionToState(selection,
|
|
54
|
+
const selectedState = translateSelectionToState(selection, state);
|
|
50
55
|
return selectedState;
|
|
51
56
|
}
|
|
52
|
-
return
|
|
57
|
+
return state;
|
|
53
58
|
};
|
|
54
59
|
const initialize = (url, _set = set) => {
|
|
60
|
+
const fallback = () => fn(_set, get, api);
|
|
55
61
|
try {
|
|
56
|
-
const queryString = url.split('?')[1]
|
|
62
|
+
const queryString = url.split('?')[1];
|
|
57
63
|
if (!queryString) {
|
|
58
|
-
return
|
|
64
|
+
return fallback();
|
|
65
|
+
}
|
|
66
|
+
const idx = queryString.indexOf(defaultedOptions.key + '=');
|
|
67
|
+
if (idx === -1) {
|
|
68
|
+
return fallback();
|
|
59
69
|
}
|
|
60
|
-
const
|
|
70
|
+
const toParse = queryString.substring(idx + 2);
|
|
71
|
+
if (!toParse) {
|
|
72
|
+
return fallback();
|
|
73
|
+
}
|
|
74
|
+
console.log('toParse', toParse);
|
|
75
|
+
const parsed = parse(toParse);
|
|
61
76
|
const currentValue = get() ?? fn(_set, get, api);
|
|
62
|
-
const merged = mergeWith(currentValue, parsed);
|
|
77
|
+
const merged = mergeWith(currentValue, getSelectedState(parsed));
|
|
63
78
|
set(merged, true);
|
|
64
79
|
return merged;
|
|
65
80
|
}
|
|
@@ -73,20 +88,36 @@ const queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
73
88
|
};
|
|
74
89
|
if (typeof window !== 'undefined') {
|
|
75
90
|
const setQuery = () => {
|
|
76
|
-
const selectedState = getSelectedState();
|
|
91
|
+
const selectedState = getSelectedState(get());
|
|
77
92
|
if (!selectedState) {
|
|
78
93
|
return;
|
|
79
94
|
}
|
|
80
95
|
const compactedSelectedState = compact(selectedState, initialState);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
const stringified = stringify(compactedSelectedState);
|
|
86
|
-
if (stringified) {
|
|
96
|
+
const stringified = compactedSelectedState && stringify(compactedSelectedState);
|
|
97
|
+
const currentQueryString = window.location.search.slice(1);
|
|
98
|
+
if (stringified || currentQueryString) {
|
|
87
99
|
// console.log('set query', stringified);
|
|
88
100
|
// console.log('parse query', parse(stringified));
|
|
89
|
-
|
|
101
|
+
// parse current querystring
|
|
102
|
+
// split query string to key-value
|
|
103
|
+
const variables = {};
|
|
104
|
+
const currentQuery = currentQueryString.split('&');
|
|
105
|
+
for (const variable of currentQuery) {
|
|
106
|
+
if (stringified?.includes(variable)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const [key, value] = variable.split('=');
|
|
110
|
+
variables[key] = value;
|
|
111
|
+
}
|
|
112
|
+
variables[defaultedOptions.key] = stringified;
|
|
113
|
+
const newQueryString = Object.keys(variables) // filter out empty values
|
|
114
|
+
.filter(key => variables[key])
|
|
115
|
+
// join key-value pairs
|
|
116
|
+
.map(key => `${key}=${variables[key]}`)
|
|
117
|
+
// join all pairs with &
|
|
118
|
+
.join('&');
|
|
119
|
+
console.log('newQueryString', newQueryString);
|
|
120
|
+
window.history.replaceState(null, '', newQueryString ? `?${newQueryString}` : window.location.pathname);
|
|
90
121
|
}
|
|
91
122
|
};
|
|
92
123
|
//TODO: find a better way to do this
|