Skip to content

Instantly share code, notes, and snippets.

@TimDeve
Created February 17, 2020 13:52
Show Gist options
  • Save TimDeve/badf8bbc91c1d64e49f8634518506307 to your computer and use it in GitHub Desktop.
Save TimDeve/badf8bbc91c1d64e49f8634518506307 to your computer and use it in GitHub Desktop.
useGateway hook with API request type safety
import * as t from 'io-ts';
import { safeGetWithValidation } from './safeRequests';
export const Data = t.type({
data: t.string
});
export type Data = t.TypeOf<typeof Data>;
export const defaultData: Data = {
data: ""
};
export async function fetchData(): Promise<Data> {
return await safeGetWithValidation("/api/v1/data", Data);
}
import React from 'react';
import useGateway from './useGateway'
function DataPage(): ReactElement | null {
const [loading, error, data] = useGateway(fetchData, defaultData);
if (loading) return null;
if (RequestError.is(error) && error.httpStatus === 404) {
return <div>No data found</div>;
} else if (error) {
// eslint-disable-next-line no-console
console.error(error);
return <div>Can't fetch data</div>;
}
return <div>{data}</div>
}
import axios, { AxiosResponse, AxiosError } from 'axios';
import { Type } from 'io-ts';
import { PathReporter } from 'io-ts/lib/PathReporter';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isAxiosError(e: any): e is AxiosError {
return e?.isAxiosError;
}
export class RequestError extends Error {
public constructor(
message: string,
public code?: string,
public httpStatus?: number,
public response?: AxiosResponse
) {
super(message);
}
public static is(something: unknown): something is RequestError {
return something instanceof RequestError;
}
}
export async function safeGet(url: string, params?: object): Promise<unknown> {
try {
const res = await axios.get(url, { params });
return res.data;
} catch (e) {
if (isAxiosError(e)) {
throw new RequestError(e.message || 'Request Error', e.code, e?.response?.status, e.response);
} else {
throw e;
}
}
}
export class TypeValidationError extends Error {
public constructor(public validationErrors: string[]) {
super(`Given Type does not match: \n${validationErrors.join('\n')}`);
}
public static is(something: unknown): something is TypeValidationError {
return something instanceof TypeValidationError;
}
}
export async function safeGetWithValidation<T, I>(url: string, type: Type<T, I>, params?: object): Promise<T> {
const data = await safeGet(url, params);
if (type.is(data)) {
return data;
} else {
const result = type.decode(data);
throw new TypeValidationError(PathReporter.report(result));
}
}
import { useEffect, useState } from 'react';
import { Nullable } from './typeHelpers';
import { RequestError, TypeValidationError } from './safeRequests';
type Errors = TypeValidationError | RequestError | Error;
function useGateway<T>(gateway: () => Promise<T>, defaultValue: T): [boolean, Nullable<Errors>, T] {
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Nullable<Errors>>(null);
const [data, setData] = useState<T>(defaultValue);
useEffect(() => {
setLoading(true);
(async (): Promise<void> => {
try {
setData(await gateway());
} catch (e) {
setError(e);
}
setLoading(false);
})();
}, [gateway]);
return [loading, error, data];
}
export default useGateway;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment