I am writing a custom hook to fetch some data from an API. I would like the returned data to be type-safe if possible. Can this be done with generics?
type Action = { type: 'PENDING' } | { type: 'SUCCESS'; payload: any } | { type: 'FAIL' };
interface State {
isLoading: boolean;
isError: boolean;
data: any;
}
const dataFetchReducer = (state: State, action: Action): State => {
switch (action.type) {
case 'PENDING':
return {
...state,
isLoading: true,
};
case 'SUCCESS': {
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
}
case 'FAIL':
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error('Action not supported');
}
};
const baseUrl = 'http://localhost:4000';
function useDataFetchFromAPI(initUrl: string, initData: any) {
const [url, setUrl] = useState(`${baseUrl}${initUrl}`);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initData,
});
// effect only runs if url changes
// see deps array (2nd argument)
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'PENDING' });
try {
const result = await axios(url);
dispatch({ type: 'SUCCESS', payload: result.data });
} catch (error) {
dispatch({ type: 'FAIL' });
}
};
fetchData();
}, [url]);
const executeFetch = (url: string) => {
setUrl(url);
};
return { ...state, executeFetch };
}
Usage would be useDataFetchFromAPI('url', [])
.
I figured you could do something like useDataFetchFromAPI < SomeType > ()
and pass it through but I'm unsure on the implementation.
import axios, { AxiosPromise } from 'axios';
import { FC, useEffect, useReducer, useState } from 'react';
type Action<T> = { type: 'PENDING' } | { type: 'SUCCESS'; payload: T } | { type: 'FAIL' };
interface State<T> {
isLoading: boolean;
isError: boolean;
data: T;
}
const createDataFetchReducer = <T>() => (state: State<T>, action: Action<T>): State<T> => {
switch (action.type) {
case 'PENDING':
return {
...state,
isLoading: true,
};
case 'SUCCESS': {
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
}
case 'FAIL':
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error('Action not supported');
}
};
const baseUrl = 'http://localhost:4000';
function useDataFetchFromAPI<T>(initUrl: string, initData: T) {
const [url, setUrl] = useState(`${baseUrl}${initUrl}`);
const dataFetchReducer = createDataFetchReducer<T>();
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initData,
});
// effect only runs if url changes
// see deps array (2nd argument)
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'PENDING' });
try {
const axiosPromise: AxiosPromise<T> = axios(url);
const result = await axiosPromise;
dispatch({ type: 'SUCCESS', payload: result.data });
} catch (error) {
dispatch({ type: 'FAIL' });
}
};
fetchData();
}, [url]);
const executeFetch = (url: string) => {
setUrl(url);
};
return { ...state, executeFetch };
}
const MyComponent: FC<{}> = props => {
type Response = { foo: number; }
const x = useDataFetchFromAPI<Response>('/foo', {
foo: 1
});
x.data.foo;
return null;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With