Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/get-scope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,32 @@ describe("getClientScope", () => {
expect(clientScopeOne.getState(longUpFx.inFlight)).toEqual(0);
expect(clientScopeOne.getState($specialData)).toEqual(getFixedDate());
});
test("shallow navigation to same page", async () => {
const serverScope = fork();

await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });
await allSettled(up, { scope: serverScope });

const values = serialize(serverScope);

const clientScopeOne = getClientScope(values);

expect(clientScopeOne.getState($count)).toEqual(3);

await allSettled(up, { scope: clientScopeOne });

expect(clientScopeOne.getState($count)).toEqual(4);

// This imitates shallow navigation to same page, e.g. with different query params
//
// Next.js will reuse the same pageProps instance in this case
// which will basically override current page state with initial one
//
// So we need to basically just ignore it, because
// we already have the latest state in the client scope
const clientScopeTwo = getClientScope(values);

expect(clientScopeTwo.getState($count)).toEqual(4);
});
});
55 changes: 38 additions & 17 deletions src/get-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,31 @@ function getServerScope(values?: Values) {
* This temporary solution on hacks allows us to solve the pain of library users when working with Next.js, as well as gather much more information to develop a better API.
*/
const _currentScope: Scope = fork();
let prevValues: Values;
/**
* @private
*
* exported for tests only
*/
export function getClientScope(values?: Values) {
if (!values) return _currentScope;
if (
!values ||
/**
* This is a hack to handle edge cases with shallow navigation
*
* In this case Next.js will basically re-use old pageProps,
* but we already have latest state in the client scope
*
* So this update is just skipped
*/
values === prevValues
)
return _currentScope;

/**
* Saving previous values to handle edge cases with shallow navigation
*/
prevValues = values;

HACK_injectValues(_currentScope, values);
HACK_updateScopeRefs(_currentScope, values);
Expand All @@ -39,23 +57,26 @@ function HACK_updateScopeRefs(scope: Scope, values: Values) {
// @ts-expect-error
for (const id in scope.reg) {
// @ts-expect-error
const ref = scope.reg[id];
if (!ref.meta || (!ref.meta?.named && ref.meta?.derived)) {
/**
* Force recalculation of derived values
*/
if (Object.hasOwnProperty.call(scope.reg, id)) {
// @ts-expect-error
delete scope.reg[id];
} else {
/**
* Update non-derived values
*/
const sid = ref?.meta?.sid;
if (sid && sid in values) {
const serialize = ref?.meta?.serialize;
const read =
serialize && serialize !== "ingore" ? serialize?.read : null;
ref.current = read ? read(values[sid]) : values[sid];
const ref = scope.reg[id];
if (!ref.meta || (!ref.meta?.named && ref.meta?.derived)) {
/**
* Force recalculation of derived values
*/
// @ts-expect-error
delete scope.reg[id];
} else {
/**
* Update non-derived values
*/
const sid = ref?.meta?.sid;
if (sid && sid in values) {
const serialize = ref?.meta?.serialize;
const read =
serialize && serialize !== "ingore" ? serialize?.read : null;
ref.current = read ? read(values[sid]) : values[sid];
}
}
}
}
Expand Down