Skip to content

Fix vue router regression #13040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 29, 2025
Merged

Conversation

farmaazon
Copy link
Contributor

@farmaazon farmaazon commented May 6, 2025

Pull Request Description

A workaround for #13039

The problems were caused by two factors:

  1. I missed a place where we already navigated in a react transition;
  2. The searchParams were modified "in place" in useSearchParamsState what caused unpredictable effects.

See PR comments to see what should be improved to close the task.

Important Notes

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
    - [ ] Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
    - [ ] Unit tests have been written where possible.
    - [ ] If meaningful changes were made to logic or tests affecting Enso Cloud integration in the libraries,
    or the Snowflake database integration, a run of the Extra Tests has been scheduled.
    • If applicable, it is suggested to paste a link to a successful run of the Extra Tests.

@farmaazon farmaazon self-assigned this May 6, 2025
@farmaazon farmaazon added the CI: No changelog needed Do not require a changelog entry for this PR. label May 6, 2025
Copy link

github-actions bot commented May 6, 2025

🧪 Storybook is successfully deployed!

📊 Dashboard:

@farmaazon farmaazon requested a review from somebody1234 May 6, 2025 11:37
@farmaazon
Copy link
Contributor Author

The integration tests discovered a regression with pre-populate email fields in forms. Added a fix, and re-requested review to just make sure you're ok with it.

@@ -89,6 +89,10 @@ export default function Registration() {
},
})

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is clearly a bad thing

When you add use 'useEffect' to set initial values - you better ask yourself, where the root cause, and solve it first.

Copy link
Contributor Author

@farmaazon farmaazon May 6, 2025

Choose a reason for hiding this comment

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

The root cause:

defaultValues are cached. To reset them, use the reset API.

with the fact that query search is deferred now, so we initialize it with old value

So I assumed this fix is just "a way of doing" in react forms. Additionally, it has an effect that query navigation inside page will also update the email field (not sure if desirable, but I think it is neat).

Do you prefer making navigating to login page non-transitional? Or have another solution in mind?

Copy link
Contributor

@MrFlashAccount MrFlashAccount May 6, 2025

Choose a reason for hiding this comment

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

Initial value is supposed to be present by the moment of initial rendering. Otherwise you'll keep facing issues like this one. Also, React transitions should be preserved as well, as you claimed in the vue router PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed the fix. Now no useEffects are needed.

Also, React transitions should be preserved as well, as you claimed in the vue router PR.

This is not what I've written, BTW. I said that these use cases for react transitions may be implemented differenttly

Copy link
Contributor

Choose a reason for hiding this comment

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

Second point is not an option for us

// would be keeping the "intermediate" state by ourselves and leave window.location.search
// management to router.
if (options.replace ?? false) {
window.history.replaceState(null, '', `?${nextSearchParams.toString()}`)
Copy link
Contributor

Choose a reason for hiding this comment

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

This can't be a solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What exactly cannot be a solution?

If "using window.location.search" to keep state of current query, then it is how it worked before:

const params = new URLSearchParams(window.location.search)
It just relied on navigate updating window.location.search synchronously, while now it is asynchronous.

I may implement better solution, not relying on window.location.search (as in TODO), but wanted to merge a quick fix first.

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a reference to a code where we update window.location directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you add a reference to a code where we update window.location directly?

There is no such code. Please, re-read my message carefully: I said keeping state of current query. Previous code was relying on navigate setting window.location.search synchronously. This is not something guaranteed, according to my research (but I might be wrong).

Copy link
Contributor

Choose a reason for hiding this comment

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

In react-router it's always synchronous, same true for window.replaceState(...). In the code you mentioned it serves a different purpose - in some edge cases we can have an outdated value in react (because react update states in batch), see: https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state

react-router doesn't provide an option to get the current pending value so we need to read the location directly.


My initial comment was about the workaround with window.history.replaceState and it's obviously not a solution and I won't let it merged that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In react-router it's always synchronous

I wasn't sure, when reading documentation. navigate may return Promise<void>: https://reactrouter.com/api/hooks/useNavigate#signature

in some edge cases we can have an outdated value in react (because react update states in batch),

Yes, I know. This is exactly what I meant by "we want window.location.href to be updated immediately, so any subsequent query changes won't override this one."

My initial comment was about the workaround with window.history.replaceState and it's obviously not a solution and I won't let it merged that way.

For me is no so obvious. Why reading window.location.search is ok, but writing window.location.search is not ok?

Copy link
Contributor

@MrFlashAccount MrFlashAccount May 6, 2025

Choose a reason for hiding this comment

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

  1. It's indirect.
  2. It happens outside of the router context
  3. It runs states out-of-sync
  4. It's not ideomatic.
  5. This is clearly a hack
  6. I spent the last year getting rid of code like this.

Reading is fine because we use it only as a pending state.

@@ -40,7 +40,8 @@ export function useSearchParamsState<T = unknown>(
defaultValue: T | (() => T),
predicate: (unknown: unknown) => unknown is T = (unknown): unknown is T => true,
): SearchParamsStateReturnType<T> {
const { router, searchParams } = useRouterInReact()
const { router, searchParams: searchParamsRaw } = useRouterInReact()
const searchParams = React.useDeferredValue(searchParamsRaw)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you add 'useDefferedValue' here? It exists for a different purpose

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Different purpose than what?

Copy link
Contributor

Choose a reason for hiding this comment

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

You clearly don't need it here because the only purpose for this hook is deferring rendering for the parts of the UI. Why do you want to defer here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When changing category or current id (the state being kept in search params) we want to defer updating asset table. I thought that it should be the default for every query update. But perhaps you're right, I should use it near actual suspense queries.

Copy link
Contributor

@MrFlashAccount MrFlashAccount May 6, 2025

Choose a reason for hiding this comment

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

In that case we should wrap category change in 'startTransition' (I'm sure we do this already)

Deferred state has completely different purpose compared to 'startTransition'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deferred state has completely different purpose compared to 'startTransition'

Didn't know that. I was just reading in startTransition docs, that:

You can wrap an update into a Transition only if you have access to the set function of that state. If you want to start a Transition in response to some prop or a custom Hook return value, try useDeferredValue instead.

ATM the category is based on the context change coming from vue.

@MrFlashAccount
Copy link
Contributor

CleanShot 2025-05-06 at 18 01 39@2x

I'm not even talking about 2 heavy rerenders instead of one previously

Copy link

github-actions bot commented May 29, 2025

✨ GUI Checks Results

Summary

  • 🧹 Prettier: ✅ Passed
  • 🧰 GUI Checks: ✅ Passed
  • 📚 Storybook: ✅ Deployed

All checks passed! Your changes look good to go.

See individual check results for more details.

ℹ️ Chromatic Tests Skipped

Chromatic visual regression tests were skipped for this PR.

To run the tests and deploy Storybook, add the CI: Run Chromatic label to this PR.

Note: We skip tests by default to optimize CI costs. Only run them when your UI changes are ready for review.

👮 Lint GUI Results

Check Results

  • 🧠 Typecheck: ✅ Passed
  • 🧹 Lint: ✅ Passed
  • 🧪 Unit Tests: ✅ Passed

🎭 Playwright Test Results

Summary

  • ⏳ Duration: 5m 44s
  • ✅ Passed: 344 tests
  • 🔴 Failed: 0 tests
  • ⚠️ Flaky: 0 tests

📥 Download Detailed Report

All tests passed! Your changes look good.

@farmaazon farmaazon added the CI: Ready to merge This PR is eligible for automatic merge label May 29, 2025
@mergify mergify bot merged commit e691cf4 into develop May 29, 2025
66 of 68 checks passed
@mergify mergify bot deleted the wip/farmaazon/vue-router-regression branch May 29, 2025 14:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: No changelog needed Do not require a changelog entry for this PR. CI: Ready to merge This PR is eligible for automatic merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants