Skip to content

Conversation

rmarscher
Copy link
Member

For #1437.

Copy link

vercel bot commented Jun 5, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
waku ✅ Ready (Inspect) Visit Preview Jul 9, 2025 1:14pm

Copy link

codesandbox-ci bot commented Jun 5, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

dai-shi added a commit that referenced this pull request Jun 6, 2025
Part of the fix for #1437.

It turns out that #1437 is reporting multiple issues:

1. Showing preview element content instead of a transition when
navigating to a previously visited page. That issue is replicated with
the e2e tests in this PR and fixed.
2. Updating the url before changing the route. I'm expecting the url to
not update until the new page has loaded. I opened #1459.
3. The changeRoute function completes before the page component has
streamed any data.

The third issue is hard to test without using unstable_events.

---------

Co-authored-by: daishi <[email protected]>
@dai-shi dai-shi mentioned this pull request Jun 6, 2025
@rmarscher
Copy link
Member Author

Why does 9791f07 cause e2e tests like "broken-links: static server > redirect" to fail? Is there some behavior that depends on the history update?

I commented out the new e2e tests for scroll position and url updating so we can focus on that issue. Then maybe we can figure out how to fix the refetch promise in changeRoute and re-enable these e2e tests.

@dai-shi
Copy link
Member

dai-shi commented Jun 20, 2025

@rmarscher Please merge main.

@rmarscher
Copy link
Member Author

I think I got a little closer by using finally to make sure it always reaches the code that updates the url. However with a client-side redirect, it seems to switch to the final url and then back to the originally requested url.

So with the broken-links example, running it with a dev server, click on "Correct redirect" and the url history will go to /exists and then /redirect. It would be nice to know that a redirect changed it somehow and skip updating.

I added RSC redirects in 4265f05 to be able to test it with a dev server. The RSC redirect was only in the public/serve.json.

@dai-shi
Copy link
Member

dai-shi commented Jun 23, 2025

If things around redirect handling seem too hard to implement or too complicated to maintain, I think our idea should be to reduce the requirement. For example, my understanding around client redirect was not enough when we added the support for redirect config in serve.json, which is technically outside the framework and which depends on rsc path convention. Maybe, it's time to revisit. (and, should do before v1-alpha.)

@dai-shi
Copy link
Member

dai-shi commented Jul 1, 2025

@rmarscher What's the current status?

@rmarscher rmarscher changed the title wip: update url after route change fix: update url after route change Jul 4, 2025
@rmarscher
Copy link
Member Author

@dai-shi I think all of the tests are passing except one. Router events seem to be firing twice on the first navigation after loading a dynamic page. They do not do that on subsequent page loads or if the static page is loaded first.

cc/ @tylersayshi

e2e/use-router.spec.ts:119:5 › useRouter › calls route change event handlers › on dynamic pages 

    Error: expect(received).toEqual(expected) // deep equality

    - Expected  - 0
    + Received  + 2

      Array [
        "Route change started",
    +   "Route change started",
    +   "Route change completed",
        "Route change completed",
      ]

@rmarscher
Copy link
Member Author

changeRoute is called twice on first navigation to /static after loading /dynamic in the use-router fixture.

I found that this is because of the "locationListeners". After the RSC fetch returns, it is looking at the routeData and then running locationListeners, if necessary. I think this is to support server-side redirects or just an extra check to make sure that the window.location and current page elements are in sync?

Before this PR, the location was updated before this code was hit so the check to see if the location update passed. The one labeled - "FIXME this check here seems ad-hoc" ;-)

Now it's failing so we'll need another way to determine if we are already in the middle of a route change.

@rmarscher
Copy link
Member Author

It's looking good now @dai-shi @tylersayshi. Maybe just a flaky windows test failure.

Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rmarscher Thanks for working on this! It looks pretty clean. Just left a few comments.

@tylersayshi Would you like to give a review with another eye?

Copy link
Member

@tylersayshi tylersayshi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rest lgtm 👍

Comment on lines 4 to 9
const redirects = {
'/redirect': '/exists',
'/RSC/R/redirect.txt': '/RSC/R/exists.txt',
'/broken-redirect': '/broken',
'/RSC/R/broken-redirect.txt': '/RSC/R/broken.txt',
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be declared outside the function scope? or does it not really matter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 Yeah, probably. technically the /RSC/R prefix and .txt extensions should probably be imported from waku constants or use a function that converts a normal path to an RSC path.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dai-shi what do you think about exposing encodeRscPath() for this?

@@ -482,6 +491,18 @@ const Redirect = ({ to, reset }: { to: string; reset: () => void }) => {
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: Is the setTimeout hack still needed with the new route change behavior?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried and there are e2e tests that break if I remove it.

Comment on lines 834 to 850
try {
await changeRoute(parseRoute(url), {
skipRefetch: true,
shouldScroll: false,
});
} finally {
if (path !== '/404') {
window.history.pushState(
{
...window.history.state,
waku_new_path: url.pathname !== window.location.pathname,
},
'',
url,
);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use .finally here like the other use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I should have not switched it to an async def. I'll fix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... it's so the error can fall through. I think we need it.

Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rmarscher Can you clarify again when we need .finally {} and when not?

unstable_startTransition: startTransitionFn,
});
} catch (err) {
console.error('Error while navigating to new route:', err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have console.error here too.

@rmarscher
Copy link
Member Author

rmarscher commented Jul 9, 2025

@rmarscher Can you clarify again when we need .finally {} and when not?

Now that window.history.pushState is called after awaiting changeRoute(), we need to use finally to make sure we update the url even if changeRoute throws an exception.

It is not necessarily needed for the useRouter() APIs because the developer can catch the error from changeRoute and handle it on their own.

The try { await changeRoute(); } finally { pushState } pattern is used for useTransition functions (<Link> ).

The changeRoute().catch(() => { console.error }).finally(() => { pushState }) pattern is used for useEffect and startTransition functions (<Redirect>, locationListeners ).

changeRoute is called without pushState in the popstate window event listener - because it's reacting to the url already being updated. Similarly, reload() and <NotFound> do not call pushState.

I think that covers all of the uses of changeRoute in waku/router/client.

Copy link

pkg-pr-new bot commented Jul 9, 2025

Open in StackBlitz

npm i https://pkg.pr.new/wakujs/waku@1459

commit: 68b4db3

@rmarscher
Copy link
Member Author

Hmm... the locationListeners use https://react.dev/reference/react/startTransition - they probably should use the .finally() syntax instead of async. I'm going to change that.

@rmarscher
Copy link
Member Author

OK. I think I have addressed all of the comments. Thanks for the reviews.

Copy link
Member

@dai-shi dai-shi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on it. It's a little bit unfortunate to see things become complicated, but can't be helped. Any ideas to fix/mitigate later are welcome.

@dai-shi dai-shi linked an issue Jul 9, 2025 that may be closed by this pull request
@rmarscher
Copy link
Member Author

Thanks for working on it. It's a little bit unfortunate to see things become complicated, but can't be helped. Any ideas to fix/mitigate later are welcome.

Maybe updating history should move into changeRoute.

changeRoute(url, { history: 'push' });

The history option could be 'push', 'replace' or false.

I'm not sure about unstable_startTransition in changeRoute options. I'm not sure a transition function should happen inside changeRoute. The place that calls changeRoute should wrap it in a transition - which we are doing in Link onClick. And when devs use the useRoute() APIs, they can wrap those in a useTransition hook if they want.

@dai-shi
Copy link
Member

dai-shi commented Jul 9, 2025

I'm not sure about unstable_startTransition in changeRoute options.

Maybe we don't need it in the future. One motivation was to support view transition api, but it will be supported by React natively.

@dai-shi dai-shi merged commit 43473c6 into wakujs:main Jul 9, 2025
48 of 49 checks passed
@rmarscher rmarscher deleted the fix/update-url-after-route-change branch July 9, 2025 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Routing completes before new page component has sent anything
3 participants