use-state-in-url
Version:
React hook to keep state in URL for deep linking
69 lines • 2.77 kB
JavaScript
import { useCallback, useEffect, useRef, useState } from "react";
import { deserialize, serialize } from "./serializeParams";
import { parseSearch, buildSearch, isEqual } from "./urlUtils";
function normalizeParam(param) {
return typeof param === "string"
? { name: param, defaultValue: "" }
: param;
}
function getParamValue(param, searchParams) {
const value = searchParams.get(param.name);
return value !== undefined && value !== null
? deserialize(param.defaultValue, value)
: param.defaultValue;
}
export function useStateInUrl(param, opts) {
const { location, navigate } = opts;
const locationRef = useRef(location);
const config = useRef(normalizeParam(param));
const getValue = () => {
const searchParams = parseSearch(location.search);
return getParamValue(config.current, searchParams);
};
const [value, setValue] = useState(getValue);
useEffect(() => {
locationRef.current = location;
const newValue = getValue();
if (!isEqual(newValue, value)) {
setValue(newValue);
}
}, [location.search, location.pathname]);
const createUpdater = useCallback((newValue) => (params) => {
setValue(newValue);
if (newValue === undefined || isEqual(newValue, config.current.defaultValue)) {
params.delete(config.current.name);
}
else {
const serialized = serialize(config.current.defaultValue, newValue);
params.set(config.current.name, serialized);
}
return params;
}, []);
const update = useCallback((newValue) => {
const params = parseSearch(locationRef.current.search);
createUpdater(newValue)(params);
const search = buildSearch(params);
navigate(`${locationRef.current.pathname}${search}`);
}, [createUpdater, navigate]);
return [value, update, createUpdater];
}
export function useBatchUpdate(opts) {
const { location, navigate } = opts;
const locationRef = useRef(location);
useEffect(() => {
locationRef.current = location;
}, [location]);
const batchUpdate = useCallback((updates, options = {}) => {
const { includeExisting = true, path } = options;
let params = includeExisting
? parseSearch(locationRef.current.search)
: new Map();
params = updates.reduce((acc, update) => update(acc), params);
const search = buildSearch(params);
const pathname = path || locationRef.current.pathname;
navigate(`${pathname}${search}`);
}, [navigate]);
return { batchUpdate };
}
export { parseSearch as parseQueryString, buildSearch as createQueryString };
//# sourceMappingURL=useStateInUrl.js.map