Skip to content

Commit

Permalink
simplify implementation, use plain encodeURIComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
nitedani committed Aug 8, 2024
1 parent b604a2b commit 983c6f0
Show file tree
Hide file tree
Showing 9 changed files with 455 additions and 772 deletions.
18 changes: 9 additions & 9 deletions examples/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
"lint": "next lint"
},
"dependencies": {
"next": "14.0.3",
"@next/font": "14.0.3",
"react": "^18",
"react-dom": "^18",
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"zustand": "^4.4.7",
"zustand-querystring": "*"
"@types/node": "^20.14.14",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"next": "14.0.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.4",
"zustand": "^4.5.4",
"zustand-querystring": "0.0.20-beta.0"
}
}
22 changes: 11 additions & 11 deletions examples/rakkas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@
},
"devDependencies": {
"@rakkasjs/eslint-config": "0.6.11",
"@types/express": "^4.17.15",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"@types/express": "^4.17.21",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"rakkasjs": "0.6.12-next.2",
"typescript": "^4.9.4",
"vite": "^4.0.3",
"vite-tsconfig-paths": "^4.0.3"
"typescript": "^4.9.5",
"vite": "^4.5.3",
"vite-tsconfig-paths": "^4.3.2"
},
"dependencies": {
"immer": "^9.0.17",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zustand": "^4.3.2",
"zustand-querystring": "*"
"immer": "^9.0.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"zustand": "^4.5.4",
"zustand-querystring": "0.0.20-beta.0"
},
"version": "0.0.20-beta.0"
}
20 changes: 10 additions & 10 deletions examples/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
"preview": "vite preview"
},
"dependencies": {
"immer": "^9.0.16",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.4",
"zustand": "^4.3.2",
"zustand-querystring": "*"
"immer": "^9.0.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.0",
"zustand": "^4.5.4",
"zustand-querystring": "0.0.20-beta.0"
},
"devDependencies": {
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^2.2.0",
"typescript": "^4.6.4",
"vite": "^3.2.3"
"typescript": "^4.9.5",
"vite": "^3.2.10"
}
}
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
},
"devDependencies": {
"bumpp": "^8.2.1",
"prettier": "^3.1.0",
"turbo": "^1.4.6"
"prettier": "^3.3.3",
"turbo": "^1.13.4"
},
"engines": {}
"engines": {},
"pnpm": {
"overrides": {
"zustand-querystring": "link:./packages/zustand-querystring/"
}
},
"packageManager": "[email protected]"
}
8 changes: 4 additions & 4 deletions packages/zustand-querystring/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
"arrowParens": "avoid"
},
"devDependencies": {
"@types/lodash-es": "^4.17.6",
"@types/lodash-es": "^4.17.12",
"rimraf": "^3.0.2",
"typescript": "^4.9.3",
"zustand": "^4.3.8",
"tsup": "^6.6.3"
"tsup": "^6.7.0",
"typescript": "^4.9.5",
"zustand": "^4.5.4"
}
}
117 changes: 20 additions & 97 deletions packages/zustand-querystring/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { parse, stringify } from './parser.js';
import { mergeWith, isEqual, cloneDeep } from 'lodash-es';
import { cloneDeep, isEqual, mergeWith } from 'lodash-es';
import { StateCreator, StoreMutatorIdentifier } from 'zustand/vanilla';
import { parse, stringify } from './parser.js';

type DeepSelect<T> = T extends object
? {
Expand Down Expand Up @@ -71,35 +71,21 @@ const translateSelectionToState = <T>(selection: DeepSelect<T>, state: T) => {
}, {} as T);
};

const escapeStringRegexp = string => {
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}

return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
};

const queryStringImpl: QueryStringImpl = (fn, options?) => (set, get, api) => {
const defaultedOptions = {
key: '$',
key: 'state',
...options,
};
const { url } = defaultedOptions;

if (encodeURIComponent(defaultedOptions.key) === defaultedOptions.key) {
defaultedOptions.key = `:${defaultedOptions.key}`;
}
const escapedKey = escapeStringRegexp(defaultedOptions.key);
const stateMatcher = new RegExp(`${escapedKey}=(.*);;|${escapedKey}=(.*)$`);
const splitMatcher = new RegExp(`${escapedKey}=.*;;|${escapedKey}=.*$`);

const parseQueryString = (querystring: string) => {
const match = querystring.match(stateMatcher);
const getStateFromUrl = (url: URL) => {
const match = url.searchParams.get(defaultedOptions.key);
if (match) {
return parse(match[1] ?? match[2]);
return parse(match);
}
return null;
};
let url = defaultedOptions.url;

const getSelectedState = (state, pathname) => {
if (defaultedOptions.select) {
const selection = defaultedOptions.select(pathname);
Expand All @@ -112,19 +98,13 @@ const queryStringImpl: QueryStringImpl = (fn, options?) => (set, get, api) => {

const initialize = (url: URL, initialState) => {
try {
const queryString = url.search.substring(1);
const pathname = url.pathname;
if (!queryString) {
return initialState;
}
const parsed = parseQueryString(queryString);
if (!parsed) {
const stateFromURl = getStateFromUrl(url);
if (!stateFromURl) {
return initialState;
}

const merged = mergeWith(
cloneDeep(initialState),
getSelectedState(parsed, pathname),
getSelectedState(stateFromURl, url.pathname),
);
set(merged, true);
return merged;
Expand All @@ -135,20 +115,6 @@ const queryStringImpl: QueryStringImpl = (fn, options?) => (set, get, api) => {
};

if (typeof window !== 'undefined') {
const decodeHref = () => {
let href = location.href;
let decoded = decodeURIComponent(href);

if (
decoded.indexOf(`?${defaultedOptions.key}=`) !==
href.indexOf(`?${defaultedOptions.key}=`)
) {
href = decoded;
decoded = decodeURIComponent(href);
}

return href;
};
const initialState = cloneDeep(
fn(
(...args) => {
Expand All @@ -159,56 +125,19 @@ const queryStringImpl: QueryStringImpl = (fn, options?) => (set, get, api) => {
api,
),
);

const setQuery = () => {
const url = new URL(decodeHref());
const url = new URL(window.location.href);
const selectedState = getSelectedState(get(), url.pathname);
const currentQueryString = url.search;
const currentParsed = parseQueryString(currentQueryString);

const newMerged = {
...currentParsed,
...selectedState,
};

const splitIgnored = currentQueryString.split(splitMatcher);
let ignored = '';
for (let str of splitIgnored) {
if (!str || str === '?' || str === '&') {
continue;
}
if (str.startsWith('&') || str.startsWith('?')) {
str = str.substring(1);
}
if (str.endsWith('&')) {
str = str.substring(0, str.length - 1);
}
ignored += (ignored ? '&' : '?') + str;
}

const newCompacted = compact(newMerged, initialState);
let newQueryString = '';
const newCompacted = compact(selectedState, initialState);
const previous = url.search;
if (Object.keys(newCompacted).length) {
const stringified = stringify(newCompacted);
const newQueryState = `${defaultedOptions.key}=${stringified};;`;
if (currentParsed) {
newQueryString = currentQueryString.replace(
splitMatcher,
newQueryState,
);
} else if (ignored) {
newQueryString = ignored + '&' + newQueryState;
} else {
newQueryString = '?' + newQueryState;
}
url.searchParams.set(defaultedOptions.key, stringify(newCompacted));
} else {
newQueryString = ignored;
url.searchParams.delete(defaultedOptions.key);
}

const currentUrl = location.pathname + location.search;
const newUrl = location.pathname + newQueryString;

if (newUrl !== currentUrl) {
history.replaceState(history.state, '', newUrl);
if (url.search !== previous) {
history.replaceState(history.state, '', url);
}
};

Expand All @@ -233,14 +162,8 @@ const queryStringImpl: QueryStringImpl = (fn, options?) => (set, get, api) => {
setQuery();
};

return initialize(new URL(decodeHref()), initialState);
return initialize(new URL(window.location.href), initialState);
} else if (url) {
url = decodeURIComponent(url);
const idx = url.indexOf(`?${defaultedOptions.key}=`);
const decoded = decodeURIComponent(url);
if (decoded.indexOf(`?${defaultedOptions.key}=`) !== idx) {
url = decoded;
}
return initialize(new URL(url, 'http://localhost'), fn(set, get, api));
}

Expand Down
Loading

0 comments on commit 983c6f0

Please sign in to comment.