zustand-querystring 0.0.11 → 0.0.13
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 +12 -2
- package/lib/middleware.js +38 -21
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# zustand-querystring
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A Zustand middleware that syncs the store with the querystring.
|
|
4
4
|
|
|
5
5
|
Try on [StackBlitz](https://stackblitz.com/github/nitedani/zustand-querystring/tree/main/examples/react) (You need to click "Open in New Tab")
|
|
6
6
|
|
|
7
|
+
Examples:
|
|
8
|
+
- [React](./examples/react/)
|
|
9
|
+
- [NextJS](./examples/next/)
|
|
10
|
+
- [Rakkas](./example/rakkas/)
|
|
11
|
+
|
|
7
12
|
Quickstart:
|
|
8
13
|
```ts
|
|
9
14
|
import create from "zustand";
|
|
@@ -48,4 +53,9 @@ export const useStore = create<Store>()(
|
|
|
48
53
|
}
|
|
49
54
|
)
|
|
50
55
|
);
|
|
51
|
-
```
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
querystring options:
|
|
59
|
+
- <b>select</b> - the select option controls what part of the state is synced with the query string
|
|
60
|
+
- <b>key: string</b> - the key option controls how the state is stored in the querystring (default: $)
|
|
61
|
+
- <b>url</b> - the url option is used to provide the request url on the server side render
|
package/lib/middleware.js
CHANGED
|
@@ -20,22 +20,26 @@ const compact = (newState, initialState) => {
|
|
|
20
20
|
});
|
|
21
21
|
return output;
|
|
22
22
|
};
|
|
23
|
-
const translateSelectionToState = (selection, state) =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return acc;
|
|
23
|
+
const translateSelectionToState = (selection, state) => {
|
|
24
|
+
if (typeof state !== 'object' || !state) {
|
|
25
|
+
return {};
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
acc[key] = state[key];
|
|
27
|
+
return Object.keys(selection).reduce((acc, key) => {
|
|
28
|
+
if (!(key in state)) {
|
|
29
|
+
return acc;
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
31
|
+
const value = selection[key];
|
|
32
|
+
if (typeof value === 'boolean') {
|
|
33
|
+
if (value) {
|
|
34
|
+
acc[key] = state[key];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
acc[key] = translateSelectionToState(value, state[key]);
|
|
39
|
+
}
|
|
40
|
+
return acc;
|
|
41
|
+
}, {});
|
|
42
|
+
};
|
|
39
43
|
const escapeStringRegexp = string => {
|
|
40
44
|
if (typeof string !== 'string') {
|
|
41
45
|
throw new TypeError('Expected a string');
|
|
@@ -48,7 +52,7 @@ const queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
48
52
|
...options,
|
|
49
53
|
};
|
|
50
54
|
const stateMatcher = new RegExp(`${escapeStringRegexp(defaultedOptions.key)}=(.*);;`);
|
|
51
|
-
const
|
|
55
|
+
const splitMatcher = new RegExp(`${escapeStringRegexp(defaultedOptions.key)}=.*;;`);
|
|
52
56
|
const parseQueryString = querystring => {
|
|
53
57
|
const match = querystring.match(stateMatcher);
|
|
54
58
|
if (match) {
|
|
@@ -96,31 +100,44 @@ const queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
96
100
|
if (typeof window !== 'undefined') {
|
|
97
101
|
const setQuery = () => {
|
|
98
102
|
const selectedState = getSelectedState(get(), location.pathname);
|
|
99
|
-
const currentQueryString = location.search
|
|
103
|
+
const currentQueryString = location.search;
|
|
100
104
|
const currentParsed = parseQueryString(currentQueryString);
|
|
101
105
|
const newMerged = {
|
|
102
106
|
...currentParsed,
|
|
103
107
|
...selectedState,
|
|
104
108
|
};
|
|
105
|
-
const
|
|
109
|
+
const splitIgnored = currentQueryString.split(splitMatcher);
|
|
110
|
+
let ignored = '';
|
|
111
|
+
for (let str of splitIgnored) {
|
|
112
|
+
if (!str || str === '?' || str === '&') {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (str.startsWith('&') || str.startsWith('?')) {
|
|
116
|
+
str = str.substring(1);
|
|
117
|
+
}
|
|
118
|
+
if (str.endsWith('&')) {
|
|
119
|
+
str = str.substring(0, str.length - 1);
|
|
120
|
+
}
|
|
121
|
+
ignored += (ignored ? '&' : '?') + str;
|
|
122
|
+
}
|
|
106
123
|
const newCompacted = compact(newMerged, initialState);
|
|
107
124
|
if (Object.keys(newCompacted).length) {
|
|
108
125
|
const stringified = stringify(newCompacted).substring(1);
|
|
109
126
|
const newQueryState = `${defaultedOptions.key}=${stringified};;`;
|
|
110
127
|
let newQueryString = '';
|
|
111
128
|
if (currentParsed) {
|
|
112
|
-
newQueryString = currentQueryString.replace(
|
|
129
|
+
newQueryString = currentQueryString.replace(splitMatcher, newQueryState);
|
|
113
130
|
}
|
|
114
131
|
else if (ignored) {
|
|
115
132
|
newQueryString = ignored + '&' + newQueryState;
|
|
116
133
|
}
|
|
117
134
|
else {
|
|
118
|
-
newQueryString = newQueryState;
|
|
135
|
+
newQueryString = '?' + newQueryState;
|
|
119
136
|
}
|
|
120
|
-
history.replaceState(history.state, '', location.pathname +
|
|
137
|
+
history.replaceState(history.state, '', location.pathname + newQueryString);
|
|
121
138
|
}
|
|
122
139
|
else {
|
|
123
|
-
history.replaceState(history.state, '', location.pathname +
|
|
140
|
+
history.replaceState(history.state, '', location.pathname + ignored);
|
|
124
141
|
}
|
|
125
142
|
};
|
|
126
143
|
// @ts-ignore
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zustand-querystring",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"zustand": "^4.3.2"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
+
"dev": "tsc -b --watch",
|
|
41
42
|
"build": "rimraf lib && tsc -b"
|
|
42
43
|
}
|
|
43
44
|
}
|