nandorojo / swr-react-native

React Native/React Navigation compatibility for Vercel's useSWR hook. 🐮

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

refreshInterval does not consider focus state and keeps refreshing api calls even when screen is not focused

javascripter opened this issue · comments

After investigating performance issues on my app, I found swr-react-native does not prevent APIs from being called when screen is not focused when refreshInterval is set.

This causes performance degradation especially when multiple screens are pushed onto the stack.
For example,

Screen A: polls API X
Screen B: polls API Y
Screen C: polls API Z

and user pushes from screen A -> B -> C, causing all X, Y, Z to be periodically fetched when only C is focused.

https://github.com/vercel/swr/blob/1bdec379667fbb33542532afa47688012cc5dc5b/src/utils/web-preset.ts#L31
The issue is caused by the default isVisible provided by SWR, which always returns true on RN.
By design swr-react-native only adds mutate calls on top of the SWR's original behavior, thus cannot prevent unnecessary API calls depending on focus state. I think a middleware would be able to implement the desirable behavior for refreshInterval. What do you think? @nandorojo

ATM, I'm doing something like this as a workaround.

const refreshWhenHidden = false

function useSomeData() {
  const isFocused = useIsFocused()

  return useSWR('/api', fetcher, {
     refreshInterval: !refreshWhenHidden && !isFocused ? 0 : 3000
  })
}

Should we just manually set isFocused() to the react navigation focused state inside of useSWR?

Haven't tried that approach yet in my app but I think that'll work!

const refreshWhenHidden = false

function useSomeData() {
  const { isFocused } = useNavigation()

  return useSWR('/api', fetcher, {
    refreshInterval: !refreshWhenHidden && !isFocused ? 0 : 3000,
    isFocused() {
      return isFocused()
    }
  })
}

Wouldn't that work?

I think you mean initFocus (isFocused doesn't exist in SWRConfiguration). To use initFocus the app needs to be wrapped inside <SWRConfig> since the useSWR's config argument doesn't accept initFocus.
initFocus is only called per cache initialization, so there won't be a way to call useNavigation() in each swr hook.

refreshInterval option is it's a per-hook config option so that's why I used it instead of trying to use initFocus.

what about isPaused?

I tried the below and it worked too.

    isPaused: () => !refreshWhenHidden  && !isFocused,

So, I think both approaches of setting 0 as refreshInterval and returning false in isPaused() work to prevent unnecessary polling calls.

I would use navigation.isFocused() instead of useIsFocused to avoid unnecessary renders.

Awesome. Is this something we should get into the docs or rather the code then as default behaviour?

Hi. I've created a PR that solves this issue with a new middleware API while maintaining backwards-compatible APIs. Please let me know what you think :)

cc @sreuter @nandorojo

I would use navigation.isFocused() instead of useIsFocused to avoid unnecessary renders.

We tried this approach but ran into some issues. In particular, when we isPaused on the hook in one screen, it also freezes updates in other active screens that consume exactly the same data.

@javascripter Do you think the middleware approach should work better for this kind of scenario?

@sreuter

In particular, when we isPaused on the hook in one screen, it also freezes updates in other active screens that consume exactly the same data.

Hmm. I wasn't aware of the issue. Does that happen if you use useIsFocused() too?

  const isFocused = useIsFocused()

  return useSWR('/api', fetcher, {
    isPaused: () => !isFocused
  })

I'm trying to understand the cause but I can't reproduce the behavior in the sandbox below. I guess it's related to when isPaused gets called (maybe we need to cause re-render anyway to tell SWR the focus state change).

https://codesandbox.io/s/exciting-aryabhata-lbk44f?file=/src/App.tsx

@javascripter Do you think the middleware approach should work better for this kind of scenario?

I can fix the middleware implementation using the above or my original refreshInterval option approach and we can keep the same middleware interface.

I like the middleware approach! It looks like SWR now allows hook middleware, I didn’t realize that change. It previously didn’t when I first made the library.

All that said…I’m surprised to hear about that bug. I assume isPaused isn’t meant to apply globally?