vercel / swr

React Hooks for Data Fetching
https://swr.vercel.app
MIT License
30.46k stars 1.22k forks source link

Alternative way to do dependent fetching besides throwing error? #426

Closed tettoffensive closed 4 years ago

tettoffensive commented 4 years ago

I was struggling with the following until I realized that it actually had to throw an error for the second call not to happen. My /api/user call returns some data even if the user id isn't available. So calling user.id doesn't throw an error, it just returns undefined.

My solution was to make another user variable that is null if the user.id is null. Since mUser is now undefined, mUser.id will throw an error.

const { data: user } = useSWR('/api/user');
  // in order to not call the dependency, user.id must throw an error
  const mUser = user?.id ? user : null;
  const { data: block, error } = useSWR(() => `/api/users/${mUser.id}/${(blocks as string[]).join('/')}`);

I'm wondering if there's already a better way to do this or a way in which I could have dependencies wait on data to be defined?

sergiodxa commented 4 years ago

You can also use null as key of your second useSWR call.

const { data: user } = useSWR('/api/user');
const { data: block, error } = useSWR(user?.id ? `/api/users/${mUser.id}/${(blocks as string[]).join('/')}` : null);
tettoffensive commented 4 years ago

I see so if user.id evaluates to null the call will not be made, but if user.id evaluates to undefined it will.

@sergiodxa Is there a reasoning for this?

I think the docs should be updated

shuding commented 4 years ago

SWR will not fire the request if:

I think the docs is clear about that.

Note that mUser.id will always throw if mUser is null OR undefined.

tettoffensive commented 4 years ago

@shuding

The comment from the "Dependent Fetching" on https://swr.now.sh/#dependent-fetching only mentions If the function throws not or returns falsy. So I didn't find that clear. More importantly, I followed this example, and found that the second useSWR did get called when user = {} (AND therefore, user.id === undefined)

Dependent Fetching

SWR allows you to fetch data that depends on other data. It ensures the maximum possible parallelism (avoiding waterfalls), as well as serial fetching when a piece of dynamic data is required for the next data fetch to happen.

import useSWR from 'swr'

function MyProjects() {
  const { data: user } = useSWR('/api/user')
  const { data: projects } = useSWR(() => '/api/projects?uid=' + user.id))
  // When passing a function, SWR will use the
  // return value as `key`. If the function throws,
  // SWR will know that some dependencies are not
  // ready. In this case it is `user`.

  if (!projects) return 'loading...'
  return 'You have ' + projects.length + ' projects'
}
shuding commented 4 years ago

@tettoffensive that's expected:

useSWR did get called when user = {}

Because {} doesn't throw, and it isn't falsy. It's expected to not pause. I think the example assumes the API will always return null if user isn't found, instead of {}. In fact I think most APIs do the same.

In your specific case, I would do this:

const { data: user } = useSWR('/api/user')
const { data: projects } = useSWR(() => {
  if (!user.id) throw new Error('user not ready')
  return '/api/projects?uid=' + user.id
})
tettoffensive commented 4 years ago

Yes you are correct user is not falsy. I was originally assuming because user.id is undefined it would not fire. But this is not the case, therefore my workaround.