-
-
Notifications
You must be signed in to change notification settings - Fork 237
fix: search hydration errors #1358
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
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
why at search page with nuxt on input, pressing enter navigates to |
|
@userquin looks like the same behavior in production now already, I can look at it as the next issue (tomorrow 🫠) |
📝 WalkthroughWalkthroughThis PR refactors the npm search composable to standardise its return payload: searchResponse, suggestions and packageAvailability are returned together via a new emptySearchPayload helper. suggestionRequestId is converted to a ref to track concurrent suggestion requests. Algolia and NPM search branches, early-return/error paths, fetchMore and initial-data integration were aligned to the consolidated payload shape. A reactive npmSuggestions path and package-availability syncing were added. Separately, useSettings now persists the search provider to a secure cookie and reads from that cookie when determining the provider. Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
app/composables/npm/useSearch.ts (1)
440-448: Variable shadowing:npmSuggestionsparameter obscures the outeruseLazyAsyncDataconst.The destructured parameter
npmSuggestionsat line 442 shadows thenpmSuggestionsconstant declared at line 427. While technically correct (the outer is only used for the watch source), this reduces readability.♻️ Suggested rename for clarity
watch( [() => asyncData.data.value.suggestions, () => npmSuggestions.data.value.suggestions], - ([algoliaSuggestions, npmSuggestions]) => { - if (algoliaSuggestions.length || npmSuggestions.length) { - suggestions.value = algoliaSuggestions.length ? algoliaSuggestions : npmSuggestions + ([algoliaSuggestions, npmSuggestionsData]) => { + if (algoliaSuggestions.length || npmSuggestionsData.length) { + suggestions.value = algoliaSuggestions.length ? algoliaSuggestions : npmSuggestionsData } }, { immediate: true }, )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
app/composables/npm/useSearch.ts (1)
443-443: Inconsistent optional chaining onasyncData.data.value.Line 443 uses
asyncData.data.value.suggestions(no optional chaining) while line 454 usesasyncData.data.value?.packageAvailability(with optional chaining). SinceemptySearchPayloadis provided as the default,asyncData.data.valueshould never be null—consider using consistent access patterns for clarity.Also applies to: 454-454
app/composables/useSettings.ts
Outdated
| const cookie = useCookie('search-provider', { | ||
| secure: true, | ||
| sameSite: 'strict', | ||
| maxAge: 60 * 60 * 24 * 30, | ||
| path: '/', | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally we wouldn't have a cookie here, so that we could cache the search page (by query key).
I think we could show algolia data in initial load, but refresh on client-side with npm data if that's what they prefer.
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be a very slow behavior. It will first download the full HTML, then clean everything up and start loading npm...
It feels like in this case - this option will be an example of a bad experience and will rather harm the service than give the user an option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I had one pretty crazy idea of adding the provider to the query before this approach. Is that suitable for us?
So, if the user has npm selected by default, every time they visit the search page, the provider will be added along with the query. But this won't work if they go directly to the search...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. This setting is now just a preference. If the user accesses the search via any mechanism within the site, the provider will also be added to the URL. Otherwise, the page will continue to use algolia until the user switches to npm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm. so I was thinking that algolia is enabled by default, so this is the edge case of the user preferring npm and hard reloading the search page rather than using client-side navigation.
the other reason was that algolia is much faster and only requires one http request rather than three, so we are more performant by skipping the npm search.
(but I guess if you have npmx as a search engine in the browser, and prefer npm, you might hit this more often than I originally expected)
we could also prerender the search page and do the fetching entirely on the client side. this would mean no danger of mismatch, very fast initial response, no double fetching....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, definitely, rare case, but sometimes the results don't load and the user reloads the page and gets one more bad experience.
I think the current solution is the most optimal in terms of scenario coverage. What do you think about latest changes?
UPD: updated a bit, now it works as I planned and is unnoticeable for the user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
app/composables/npm/useSearch.ts (1)
442-463: Inconsistent optional chaining between watchers.Line 443 accesses
asyncData.data.value.suggestionswithout optional chaining, line 454 usesasyncData.data.value?.packageAvailabilitywith optional chaining, and line 455 accessesnpmSuggestions.data.value.packageAvailabilitywithout it.While this is safe at runtime due to the default values, the inconsistency reduces readability. Consider standardising the access pattern.
♻️ Suggested consistency improvement
watch( - [() => asyncData.data.value.suggestions, () => npmSuggestions.data.value.suggestions], + [() => asyncData.data.value?.suggestions ?? [], () => npmSuggestions.data.value?.suggestions ?? []], ([algoliaSuggestions, npmSuggestionsValue]) => { if (algoliaSuggestions.length || npmSuggestionsValue.length) { suggestions.value = algoliaSuggestions.length ? algoliaSuggestions : npmSuggestionsValue } }, { immediate: true }, ) watch( [ - () => asyncData.data.value?.packageAvailability, - () => npmSuggestions.data.value.packageAvailability, + () => asyncData.data.value?.packageAvailability ?? null, + () => npmSuggestions.data.value?.packageAvailability ?? null, ],
| if (!intent || !name) { | ||
| suggestionsLoading.value = false | ||
| return { suggestions: [], packageAvailability: availability } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early return may bypass awaiting the package existence check.
When the query is a valid scoped package name like @scope/package, isValidNewPackageName returns true (line 345), but parseSuggestionIntent returns { intent: null, name: '' } because of the / check. This causes the early return at line 363 to execute before Promise.all(promises) at line 400.
The returned packageAvailability will be null even though a check was initiated. The reactive ref is eventually updated via the side effect in the .then() callback, so the final UI state is correct, but the return value from validateSuggestionsNpm is inconsistent with actual state.
🛠️ Proposed fix to await promises before early return
if (!intent || !name) {
+ if (promises.length > 0) {
+ await Promise.all(promises)
+ }
suggestionsLoading.value = false
return { suggestions: [], packageAvailability: availability }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!intent || !name) { | |
| suggestionsLoading.value = false | |
| return { suggestions: [], packageAvailability: availability } | |
| } | |
| if (!intent || !name) { | |
| if (promises.length > 0) { | |
| await Promise.all(promises) | |
| } | |
| suggestionsLoading.value = false | |
| return { suggestions: [], packageAvailability: availability } | |
| } |
There was a hydration error that caused re-rendering and repeated requests.
There were three problems:
useLazyAsyncDataand, when ready, I re-assign them into suggestions ref.