I have a hook that reads and writes to url. It adds and reads query params. Here is what it looks like
// usage
const [filtersValues, setFiltersValues] = useURLState(mergedInitialValues);
// the hook itself
export const useURLState = (initialState: any) => {
const history = useHistory();
const stringifyQuery = (queryAsObject: Record<string, any>) => {
const { date, ...rest } = queryAsObject;
const queryToStringify = ...rest;
return queryString.stringify(queryToStringify, ARRAY_FORMAT);
};
const parseQueryString = useCallback((queryParams: string) => {
const parsed = queryString.parse(queryParams, ARRAY_FORMAT);
return parsed;
}, []);
const addQueryParamsToUrl = useCallback(
(newQueryAsObject: Record<string, any>) => {
const newQuery = stringifyQuery(newQueryAsObject);
const currentQuery: string = history.location.search.slice(1); // remove leading ? in current location
// prevent infinite loop
if (currentQuery !== newQuery) {
history.push({ search: newQuery });
}
},
[history]
);
useMemo(() => {
const mergedQueryParams = {
...initialState,
...parseQueryString(history.location.search)
};
addQueryParamsToUrl(mergedQueryParams);
}, [addQueryParamsToUrl, initialState, history.location.search, parseQueryString]);
const setFiltersState = useCallback(
value => {
addQueryParamsToUrl(value);
},
[addQueryParamsToUrl]
);
// NOTE THIS USE MEMO
// it just parses search string and returns values as object for convenience
// used this way so url is the only source of data
const filtersAsPlainObject = useMemo(() => {
// query-string will not make single value an array
// e.g. ?foo=bar will never be parsed as foo = ['bar']
// therefore need to ensure it with this helper
const parsed = parseQueryString(history.location.search);
const result: Record<string, any> = getTypeSafeValues(initialState, parsed);
return merge(cloneDeep(initialState), withDateFiltersHandled);
}, [parseQueryString, initialState, history.location.search]);
return [filtersAsPlainObject, setFiltersState];
}
When I test it in browser it works fine and useMemo which returns filtersAsPlainObject always gets the latest values; However problems come when I try to test it with react-testing-library and jest.
As soon as I try to update any filters in any way, (e.g. clear them all). history.location.search is updated. I can clearly see it in my test.
E.g. BEFORE CLEANING
?grade=gradeK,grade1&location=zoom&type=camp,course
AFTER
'' // just empty string
But what is really odd to me, is that useMemo is not fired and new values are not received. So previous values are returned. Any idea what am I doing wrong here? (It was obvoius to me that when history.location.search changes useMemo should be triggered and new values should be returned from hook. But for some reason that doesn't happen)