tutorbookapp/tutorbook

View on GitHub
lib/hooks/url-param-sync.ts

Summary

Maintainability
A
0 mins
Test Coverage
import Router from 'next/router';
import { dequal } from 'dequal';
import { useEffect } from 'react';

import { Callback } from 'lib/model/callback';
import { Query } from 'lib/model/query/base';

interface Constructor<T extends Query> {
  params: (params: any) => T;
}

// Syncs the initial query state in URL parameters. This gives users a sharable
// link that contains their query state (e.g. I can share a link showing all the
// Songwriting teachers with another org admin).
// TODO: Perhaps look into directly managing state using the Next.js router.
export default function useURLParamSync<T extends Query>(
  query: T,
  setQuery: Callback<T>,
  Model: Constructor<T>,
  overrides: string[] = []
): void {
  useEffect(() => {
    setQuery((prev) => {
      if (typeof window === 'undefined') return prev;
      const searchParams = new URLSearchParams(window.location.search);
      const params = Object.fromEntries(searchParams.entries());
      const updated = Model.params({ ...prev.params, ...params });
      if (dequal(prev, updated)) return prev;
      return updated;
    });
  }, [setQuery, Model]);

  // TODO: Don't include query params that are specified in other ways (e.g. the
  // users dashboard specifies org in the `[org]` dynamic page param).
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const params = Object.entries(query.params)
      .filter(([key]) => !overrides.includes(key))
      .map((entry) => entry.join('='))
      .join('&');
    const url = `${window.location.pathname}${params ? `?${params}` : ''}`;
    if (url === `${window.location.pathname}${window.location.search}`) return;
    void Router.replace(url, undefined, { shallow: true });
  }, [query, overrides]);
}