Reputation: 2401
I'm trying to figure out if there's a way to abstract the data fetching hooks I've created, with Typescripts generics, for my React app. Unfortunately my understanding of Typescript isn't as good as I'd hope so I'm in a pickle.
The hooks I'm using are based on the example given by SWR, the data fetching library I'm using. They have an example of what I'd like to build, in their repository but it's using Axios, while I'm using fetch (or isomorphic-unfetch to be exact).
To be explicit, I have useUsers
hook to fetch all the users from my users endpoint, like so:
import useSWR from 'swr';
import fetcher from '../fetcher';
import { User } from '@prisma/client';
type UserHookData = {
appUser?: User;
isLoading: boolean;
isError: any;
};
type UserPayload = {
user?: User;
};
function useUsers(): UserHookData {
const { data, error } = useSWR<UserPayload>('/api/users/', fetcher);
const userData = data?.user;
return {
appUser: userData,
isLoading: !error && !data,
isError: error,
};
}
export default useUsers;
The fetcher function used is copied directly from one of their typescript examples and I'm using Prisma as my ORM so I can access the types through it. The User
type has fields like email and name.
I've got another, almost identical hook to fetch a single project from my project end point:
import useSWR from 'swr';
import fetcher from '../fetcher';
import { Project } from '@prisma/client';
type ProjectHookData = {
project: Project;
isLoading: boolean;
isError: any;
};
type ProjectPayload = {
project?: Project;
};
function useProject(id: number): ProjectHookData {
const { data, error } = useSWR<ProjectPayload>(`/api/project/${id}`, fetcher);
const project = data?.project;
return {
project,
isLoading: !error && !data,
isError: error,
};
}
export default useProject;
What I'd like to do is have one hook (let's call it useRequest like in the SWR example) that I can fetch most of the data I need. With regular Javascript, I'd probably write it like this:
function useRequest(url, key){
const { data: payload, error } = UseSWR(url, fetcher);
const data = payload[key]
return {
data,
isLoading: !error && !data,
isError: error
};
}
But how would I do this in Typescript, when I want to use this for multiple different data types? I thought about maybe using generics for this, but I'm unsure if it's the correct way to solve this.
Upvotes: 6
Views: 8883
Reputation: 2401
The following hook, using generics, seems to work:
import useSWR from 'swr';
import fetcher from './fetcher';
interface DataPaylaod<T> {
[key: string]: T;
}
interface DataResponse<T> {
data: T;
isLoading: boolean;
isError: any;
}
function useRequest<T>(url: string, key: string): DataResponse<T> {
const { data: payload, error } = useSWR<DataPaylaod<T>>(url, fetcher);
const data = payload ? payload[key] : undefined;
return {
data,
isLoading: !data && !error,
isError: error,
};
}
export default useRequest;
At least it passes tsc
and returns the data in the form I'm expecting. If anyone has a more elegant solution, I'd love to see it as well.
Upvotes: 9