Skip to content
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

concerns about passing null as fetcher #738

Closed
promer94 opened this issue Oct 31, 2020 · 5 comments
Closed

concerns about passing null as fetcher #738

promer94 opened this issue Oct 31, 2020 · 5 comments

Comments

@promer94
Copy link
Collaborator

Background

@pacocoursey 's idea for SWR as a local cache is great.

const useSharedState = (key, initial) => {
  const { data: state, mutate: setState } = useSWR(key, {
    initialData: initial
  })
  return [state, setState]
}

But after #367 this won't work anymore because window.fetch will be used when fn is undefined

swr/src/use-swr.ts

Lines 245 to 248 in 10e18cc

if (typeof fn === 'undefined') {
// use the global fetcher
fn = config.fetcher
}

However passing null as fetcher will work since we only replace the fetcher to window.fetch when fetcher is undefined.

useSWR(key, null ,{ initialData: initial })

But the typescript will complain this kind of usage. #730 fix this problem by accepting null as as the fetcher function.

Problem

  1. This usage is not well documented.
  2. passing null as the fetcher function is like a hack here to disable the whole stale-while-revalidate process in swr. I am not sure it is a good api design. I feel like it somehow leaks the implementation details.
  3. Passing null as the fetcher function makes most of the options useless here.
useSWR(key, null ,{ initialData: initial, /** most of options here will not be used since the revalidation is disabled here */ })

Possible alternatives

  1. a passive options and a different options type
useSWR(key, { 
  initialData: initial, 
  passive: true 
  /** typescript should complain about set options like onSuccess 
  which will only be used at  stale-while-revalidate process  */
})
  1. a new hooks share the caches
const { data: state, mutate: setState } = useSWRPassive(key, { initialData })
@loopmode
Copy link

loopmode commented Jan 4, 2021

I'd like to drop my two cents regarding API design and passing null for fetcher, even while it's only slightly related.
In my fetching hooks i typically use a concept where I can pass an option autostart or autoload which defaults to true, and my hooks typically return a load or start function, which can be used manually if autostart is set to false.
Maybe something similar could be considered in this library too. It creates a slightly larger API surface, but also provides huge flexibility and new features/possibilities, like submitting forms and any kind of user/gui-controlled fetching.

For this specific issue, it would mean instead of passing null for fetcher, you could just explicitly state that whatever the fetcher, it shouldn't be automatically invoked just because the hook was mounted.

@TommySorensen
Copy link

@promer94 I recent used the same approach but found it kind of buggy when using it as a shared hook with initialData several places with something like this:

// use-navigation.js
const initialData = {
  isOpen: false,
  searchIsOpen: false,
  searchHasItems: false,
  level: 0,
  levels: {}
}

const useNavigation = () => {
  const { data: navState, mutate } = useSWR('navigation', null, {
    initialData
  })

  const setNavState = newState => mutate({
    ...navState,
    ...newState
  }, false)

  return { navState, setNavState }
}

// Some other component
const { navState, setNavState } = useNavigation()
setNavState({ isOpen: !navState.isOpen})
// Shortly after somewhere else
setNavState({ searchHasItems: true})

The main issue was that when doing multiple mutate, to set the searchHasItems to falseor true, then the useSwr suddenly returns the initialData from previous like on the screenshot.

image

I never found a fix for it, so i just assumed the API was not really ready for doing stuff like this, and like you mention, there is not a lot of documentation saying this something i should do 🙂

@webda2l
Copy link

webda2l commented Jan 27, 2021

Instead using null as fetcher, you can give a try to:

  const { data, mutate, isValidating } = useSWR<BookingSelection>(
    'local_booking',
    (key, bookingSelection) =>
      new Promise((resolve, reject) =>
        bookingSelection ? resolve(bookingSelection) : reject(bookingSelection)
      ),
    {
      initialData: initialBookingSelection,
      revalidateOnFocus: false,
    }
  )

But in this case, isValidating is stuck to true :/ so I do:

  const { data, mutate, isValidating } = useSWR<BookingSelection>(
    'local_booking',
    (key, bookingSelection) => {
      return new Promise((resolve, reject) =>
        bookingSelection ? resolve(bookingSelection) : reject(bookingSelection)
      )
    },
    {
      initialData: initialBookingSelection,
      revalidateOnFocus: false,
    }
  )
  
  return {
    bookingSelection: data,
    bookingSelectionLoading: !isValidating,    // [Unstable] false by default, then true after first mutate, but then stuck to true, even if (mutate with reValidate true) :/
    initBookingSelection: (bookingSelection: Partial<BookingSelection>) => {
      mutate({
        ...initialBookingSelection,
        ...bookingSelection,
      })
    },
 }

If someone has better solution, I'm interested.

@lukeshumard
Copy link

lukeshumard commented Mar 29, 2021

I've spent a little bit of time playing around with this, also inspired by @pacocoursey's blog post. His approach is a very eloquent and simple solution, but it is important to consider what using null as a fetcher means.

By passing null as the fetcher, any attempts to fetch data will return as null. This sounds obvious, but it's important to remember that SWR can be configured to revalidate your data on focus and reconnect. If either of these events occur, the fetcher is called and your data is now null. Additionally, calling mutate could also trigger a revalidation so it's important to be mindful of this.

Here's my useSharedState hook based off of @shuding's suggestion.

import useSWR from 'swr'

export default function useSharedState(key, initialData) {
  return useSWR(key, null, {
    initialData,
    revalidateOnFocus: false,
    revalidateOnReconnect: false
  })
}

In use, it looks like this.

const { data, mutate } = useSharedState('username', 'luke')

As long as the fetcher is not called again, this will result in having shared state between hooks in a single page.

@shuding
Copy link
Member

shuding commented Aug 28, 2021

In SWR 1.0, there is no more default fetcher so you don't need to pass null as fetcher anymore. Also we think the fallback API is a better way to provide "initial data". I think we can close this issue now :)

@shuding shuding closed this as completed Aug 28, 2021
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

No branches or pull requests

6 participants