useMemo used to track history.location.search is not trigered in test environment

Viewed 133

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)

0 Answers
Related