Right now SWR depends on throwing errors in order to do dependent fetching:
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'
}
However, tools like TypeScript are specifically designed so you never write code like this, making it really awkward to force your type checker to allow an error to be thrown:
const { data: projects } = useSWR(
() => '/api/projects?uid=' + (user as any).id
)
In order to get around this, I've written a template tag which allows you to safely construct strings with nullable values:
let { data: user } = useSWR('/api/user.json')
let { data: projects } = useSWR(
safestring`/api/users/${user?.id}/projects.json`,
)
safestring will only return a string if every interpolated value is not null or undefined, otherwise it will return null which makes it work with no additional changes to useSWR.
Today's API in SWR is awkward even if you aren't using a type checker though. There are times when JavaScript isn't going to throw an error when you would expect it to. For example:
// This won't throw an error if `[0]` does not exist
useSWR(() => '/api/books/' + bookIds[0])
// Instead you need to throw an error (or return null):
useSWR(() => {
let first = bookIds[0]
if (first) return '/api/books/' + bookIds[0]
throw new Error()
})
These manual checks really undo any benefits of having this implicit-error-based API.
Here is an implementation of safestring:
export type SafeStringArg = string | number | null | undefined
export default function safestring(
strings: TemplateStringsArray,
...args: SafeStringArg[]
): string | null {
let result = strings[0]
for (let index = 0; index < args.length; index++) {
if (args[index] == null) return null
result += args[index] + strings[index + 1]
}
return result
}
I don't mind having this outside of SWR, but I just want to offer this up as a potential improvement to the API.
Maybe it could work like this?
let { data: user } = useSWR('/api/user')
let { data: projects } = useSWR(t => t`/api/projects?user=${user?.id}`)
It鈥檚 an interesting idea to use template tag for this!
While this throw-catch trick for dependent fetching is considered a tiny optimization, to avoid manual checks. However we do see it also brings more problems. One is TS, and the more important thing is readability and maintainability.
I鈥檇 say we should always _recommend_ explicitly throwing error / returning null, and note about the TS issue in the docs. What do you think?
Personally I'd rather see the error trick go away, but I'd also like a convenient API. So I'm probably gonna stick with the template string either way.
I think the docs might be better off advising people to return null all the time.
I personally like this idea a lot, specially this API
let { data: projects } = useSWR(t => t`/api/projects?user=${user?.id}`)
It would be handy, we use the same trick with template tag in our codebase
We recently started integrating SWR into our projects and hit this same issue with Typescript. We have gone with this approach, would this be a valid approach?
// I updated the example from the docs with our approach
function MyProjects() {
const fullUrl = (user) => {
return user != null ? "/api/projects?uid=" + user.id : null;
};
const { data: user } = useSWR("/api/user");
const { data: projects } = useSWR(() => fullUrl(user));
if (!projects) return "loading...";
return "You have " + projects.length + " projects";
}
The docs say Typescript ready, but some examples of using the framework with Typescript would help make that more truthy. Would be more than happy to help write them if you think there is a need.
Most helpful comment
I personally like this idea a lot, specially this API