Skip to content
Open
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
6 changes: 3 additions & 3 deletions src/content/learn/you-might-not-need-an-effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,11 +720,11 @@ function SearchResults({ query }) {
}
```

You *don't* need to move this fetch to an event handler.
This is a case where an Effect *is* needed because you're [synchronizing](/learn/synchronizing-with-effects) with an external system (the network). However, this is different from the earlier examples where Effects weren't needed.

This might seem like a contradiction with the earlier examples where you needed to put the logic into the event handlers! However, consider that it's not *the typing event* that's the main reason to fetch. Search inputs are often prepopulated from the URL, and the user might navigate Back and Forward without touching the input.
The key difference: here, you need to fetch data whenever `query` or `page` changes, regardless of *how* they changed. The `query` might come from the URL (when the user navigates Back/Forward), or it might be typed by the user. The `page` might change from a button click or from restoring scroll position. It doesn't matter where `page` and `query` come from—while this component is visible, you want to keep `results` synchronized with data from the network for the current `page` and `query`. This is why it's an Effect.

It doesn't matter where `page` and `query` come from. While this component is visible, you want to keep `results` [synchronized](/learn/synchronizing-with-effects) with data from the network for the current `page` and `query`. This is why it's an Effect.
In contrast, if fetching was only needed in response to a specific user action (like clicking a "Search" button), you would put it in that event handler instead.

However, the code above has a bug. Imagine you type `"hello"` fast. Then the `query` will change from `"h"`, to `"he"`, `"hel"`, `"hell"`, and `"hello"`. This will kick off separate fetches, but there is no guarantee about which order the responses will arrive in. For example, the `"hell"` response may arrive *after* the `"hello"` response. Since it will call `setResults()` last, you will be displaying the wrong search results. This is called a ["race condition"](https://en.wikipedia.org/wiki/Race_condition): two different requests "raced" against each other and came in a different order than you expected.

Expand Down
7 changes: 5 additions & 2 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ export default function MyApp({Component, pageProps}: AppProps) {
useEffect(() => {
// Taken from StackOverflow. Trying to detect both Safari desktop and mobile.
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isSafari) {
// Detect Opera (desktop uses OPR, mobile may use Opera in user agent)
const isOpera = /OPR|Opera/i.test(navigator.userAgent);
if (isSafari || isOpera) {
// This is kind of a lie.
// We still rely on the manual Next.js scrollRestoration logic.
// However, we *also* don't want Safari grey screen during the back swipe gesture.
// However, we *also* don't want Safari/Opera grey screen during the back swipe gesture.
// Seems like it doesn't hurt to enable auto restore *and* Next.js logic at the same time.
// Opera on mobile has similar scroll behavior issues as Safari, so it needs the same fix.
history.scrollRestoration = 'auto';
} else {
// For other browsers, let Next.js set scrollRestoration to 'manual'.
Expand Down