В 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
создавать результирующий евент на который и регировать в местах где нам требуется готовность квери параметров.
Это сложно и малоподдерживаемо и лишено прозрачности с точки зрения подхода как вообще проектировать завязку.
Нужна помощь комьюнити с этим вопросом, если возможно с примером кода связей, который облегчит и решит обозначенную проблему.