Skip to content

Instantly share code, notes, and snippets.

@7iomka
Created April 29, 2023 15:40
Show Gist options
  • Save 7iomka/159af75d9eb4d5a4a75b62516b357283 to your computer and use it in GitHub Desktop.
Save 7iomka/159af75d9eb4d5a4a75b62516b357283 to your computer and use it in GitHub Desktop.
Events related with next.js router - handle with effector issues

В next.js для статических страниц доступ к квери параметрам доступен только на клиенте и только после того как роутер на клиенте получит необходимые данные об этих квери параметрах (после чудо-гидрации, когда router.isReady) У нас есть такая схема инициализации юнитов для роутера

  /**
   * Gate for next router,
   * => null, when page is not mounted / on unmount
   * => router state, when page is mounted
   */
  const RouterGate = createGate<NextRouter | null>(null!);
  const routerUpdated = createEvent<NextRouter | null>();

  /**
   * Trigger routerUpdated event when `asPath` changed
   * `asPath` is a full page url including query and hash
   * handle this changes because for static pages next.js
   * parse full url only clientside after hydration and updates the router
  */
  sample({
    clock: RouterGate.state,
    source: $router,
    filter: (lastRouter, newRouter) => lastRouter?.asPath !== newRouter?.asPath,
    fn: (_, router) => (router?.asPath ? router : null),
    target: routerUpdated,
  });
  
  const $url = createStore('');

  // Set url on router initialize / `asPath` updated
  sample({
    clock: [RouterGate.open, routerUpdated],
    filter: Boolean,
    fn: (router) => router.asPath,
    target: $url,
  });

  /**
   * Prepare query params
   * NOTE: next.js `query` also contains `params` from dynamic routes,
   * So we don't use it as a source of truth!
   * Instead we will use `routerUpdated`, which is triggered whenever
   * the router changes but only when it `isReady`,
   */
  const queryParamsChangeRequested = createEvent<ParsedUrlQuery>();
  const queryParamsChangeFromEmptyRequested = createEvent<ParsedUrlQuery>();
  const queryParamsChanged = createEvent<ParsedUrlQuery>();

  const $queryParams = createStore<ParsedUrlQuery>({});

  /**
   * Update query on routerUpdated, extracted from `asPath`,
   * because we prevented uneccessary clientside updates when only `query` from router changes,
   * although `asPath` is already with query params
   * First, target into intermediate event to be able to listen to queryParams before the update
   */
  sample({
    clock: routerUpdated,
    source: $queryParams,
    filter: (currentQueryParams, router) =>
      !equal(getQueryParamsFromPath(router?.asPath), currentQueryParams),
    fn: (_, router) => getQueryParamsFromPath(router?.asPath),
    target: queryParamsChangeRequested,
  });

  sample({
    clock: queryParamsChangeRequested,
    source: $queryParams.map((v) => Object.keys(v).length === 0),
    filter: (isQueryParamsEmpty, queryParamsToChange) =>
      isQueryParamsEmpty && Object.keys(queryParamsToChange).length !== 0,
    fn: (_, queryParamsToChange) => queryParamsToChange,
    target: queryParamsChangeFromEmptyRequested,
  });

  sample({
    clock: queryParamsChangeRequested,
    target: $queryParams,
  });

  sample({
    clock: $queryParams,
    target: queryParamsChanged,
  });

В точке входа _app.tsx инициализируем гейт

  const router = useRouter();
  useGate($$navigation.RouterGate, router);

Далее, у нас есть компонент страницы HomePage в котором есть свой гейт

model

  const PageGate = createGate<{ url: string }>();

ui

 const router = useRouter();
 useGate(gate, { url: router.asPath });

Проблема: Мы хотим вызвать какой-то код по некоторому евенту из страницы У нас этим евентом будет являтся PageGate.open если мы хотим обработать инициализацию значений из квери-параметров

   // Get initial value silently
  sample({
    clock: getInitialValueOn,
    source: $$navigation.$queryParams,
    // skip wrong values
    filter: (queryParams) => {
      console.log('getInitialValueOn', JSON.stringify(queryParams));
      return (
        queryKey in queryParams &&
        typeof queryParams[queryKey] === 'string' &&
        sortingOptions.map((opt) => opt.value).includes(queryParams[queryKey] as K)
      );
    },

    fn: (queryParams) => {
      return queryParams[queryKey] as K;
    },
    target: initialValueSettled,
  });

В данной ситуации PageGate.open отработает ДО того как в $$navigation.$queryParams попадут реальные квери параметры. Если мы попытаемся извлечь их из $$navigation.url обработкой строки, то также наткнемся на случай когда они отсутствуют при триггере гейта страницы, который по задумке запускается в useLayoutEffect.

В связи с этим приходится придумывать какие-то костыли с созданием производных евентов на каждый чих и в комбинации с combineEvents создавать результирующий евент на который и регировать в местах где нам требуется готовность квери параметров. Это сложно и малоподдерживаемо и лишено прозрачности с точки зрения подхода как вообще проектировать завязку.

Нужна помощь комьюнити с этим вопросом, если возможно с примером кода связей, который облегчит и решит обозначенную проблему.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment