feat: extract types from endpoint

chore: change example and readme

Closes #24
This commit is contained in:
Dmitriy Pleshevskiy 2020-12-19 12:10:45 +03:00
parent 96c7ea554c
commit 651e09bc31
9 changed files with 37 additions and 20 deletions

View file

@ -36,7 +36,7 @@ const MoviesEndpoint: Endpoint<MoviesResponse, void> = {
type MoviesResponse = Movie[]; type MoviesResponse = Movie[];
function App() { function App() {
const { data, loading } = useRequest<MoviesResponse>(MoviesEndpoint); const { data, loading } = useRequest(MoviesEndpoint);
return !data ? ( return !data ? (
<div>{ loading ? 'Loading...' : 'Something went wrong' }</div> <div>{ loading ? 'Loading...' : 'Something went wrong' }</div>

View file

@ -21,11 +21,11 @@ export type MoviesResponse = {
items: Movie[], items: Movie[],
} }
export const MovieEndpoint: Endpoint<MovieResponse, void, MovieParams> = { export const MovieEndpoint: Endpoint<MovieResponse, never, MovieParams> = {
method: Method.GET, method: Method.GET,
url: ({ id }) => `/action-adventure/${id}`, url: ({ id }) => `/action-adventure/${id}`,
}; };
export type MovieParams = Readonly<{ id: React.Key }>; export type MovieParams = Readonly<{ id: React.Key }>;
export type MovieResponse = Movie[]; export type MovieResponse = Movie;

View file

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { useRequest } from 'react-rest-request'; import { useRequest } from 'react-rest-request';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { MovieEndpoint, MovieParams, MovieResponse, MoviesEndpoint, MoviesResponse } from './endpoint'; import { MovieEndpoint, MoviesEndpoint } from './endpoint';
export function MoviesPage() { export function MoviesPage() {
const { data, loading } = useRequest<MoviesResponse>(MoviesEndpoint); const { data, loading } = useRequest(MoviesEndpoint);
return !data ? ( return !data ? (
<div>{ loading ? 'Loading...' : 'Something went wrong' }</div> <div>{ loading ? 'Loading...' : 'Something went wrong' }</div>
@ -27,7 +27,7 @@ export function MoviesPage() {
export function MoviePage() { export function MoviePage() {
const params = useParams<{ id: string}>(); const params = useParams<{ id: string}>();
const { data, loading } = useRequest<MovieResponse, void, MovieParams>( const { data, loading } = useRequest(
MovieEndpoint, MovieEndpoint,
{ {
params, params,

View file

@ -8,9 +8,13 @@ export enum Method {
DELETE = 'DELETE', DELETE = 'DELETE',
} }
export type Endpoint<R, V, P = void> = Readonly<{ export type Endpoint<R, V, P = never> = Readonly<{
method: Method; method: Method;
url: string | ((params: P) => string); url: string | ((params: P) => string);
headers?: Record<string, string>; headers?: Record<string, string>;
transformResponseData?: (data: any) => R; transformResponseData?: (data: any) => R;
}> }>
export type ExtractEndpointResponse<E> = E extends Endpoint<infer R, any, any> ? R : E extends Endpoint<infer R, any> ? R : never;
export type ExtractEndpointVariables<E> = E extends Endpoint<any, infer V, any> ? V : E extends Endpoint<any, infer V> ? V : never;
export type ExtractEndpointParams<E> = E extends Endpoint<any, any, infer P> ? P : never;

View file

@ -2,13 +2,13 @@ import React, { useCallback, useMemo } from 'react';
import invariant from 'tiny-invariant'; import invariant from 'tiny-invariant';
import isEqual from 'lodash.isequal'; import isEqual from 'lodash.isequal';
import { useClient } from './client-hook'; import { useClient } from './client-hook';
import { Endpoint } from './endpoint'; import { Endpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables } from './endpoint';
import { PublicRequestState, RequestAction, requestReducer, RequestState } from './reducer'; import { PublicRequestState, RequestAction, requestReducer, RequestState } from './reducer';
import { useRequestContext } from './request-context'; import { useRequestContext } from './request-context';
import { ClientResponse } from './client'; import { ClientResponse } from './client';
import { isFunction } from './misc'; import { isFunction } from './misc';
export type LazyRequestConfig<R, V, P = void> = Readonly<{ export type LazyRequestConfig<R, V, P = never> = Readonly<{
variables?: V; variables?: V;
params?: P; params?: P;
headers?: Record<string, string>; headers?: Record<string, string>;
@ -23,8 +23,13 @@ export type LazyRequestHandlerConfig<R, V, P> = Readonly<
export type RequestHandler<R, V, P> = (config?: LazyRequestHandlerConfig<R, V, P>) => Promise<R | null>; export type RequestHandler<R, V, P> = (config?: LazyRequestHandlerConfig<R, V, P>) => Promise<R | null>;
export function useLazyRequest<R = Record<string, any>, V = Record<string, any>, P = void>( export function useLazyRequest<
endpoint: Endpoint<R, V, P>, E extends Endpoint<R, V, P>,
R = ExtractEndpointResponse<E>,
V = ExtractEndpointVariables<E>,
P = ExtractEndpointParams<E>
>(
endpoint: E,
config?: LazyRequestConfig<R, V, P>, config?: LazyRequestConfig<R, V, P>,
): [RequestHandler<R, V, P>, PublicRequestState<R>] { ): [RequestHandler<R, V, P>, PublicRequestState<R>] {
const [client] = useClient(); const [client] = useClient();

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import invariant from 'tiny-invariant'; import invariant from 'tiny-invariant';
import { Endpoint, Method } from './endpoint'; import { Endpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables, Method } from './endpoint';
import { LazyRequestConfig, useLazyRequest } from './lazy-request-hook'; import { LazyRequestConfig, useLazyRequest } from './lazy-request-hook';
export type RequestConfig<R, V, P> = Readonly< export type RequestConfig<R, V, P> = Readonly<
@ -10,8 +10,13 @@ export type RequestConfig<R, V, P> = Readonly<
} }
> >
export function useRequest<R = Record<string, any>, V = Record<string, any>, P = void>( export function useRequest<
endpoint: Endpoint<R, V, P>, E extends Endpoint<R, V, P>,
R = ExtractEndpointResponse<E>,
V = ExtractEndpointVariables<E>,
P = ExtractEndpointParams<E>
>(
endpoint: E,
config?: RequestConfig<R, V, P>, config?: RequestConfig<R, V, P>,
) { ) {
invariant( invariant(

View file

@ -6,9 +6,12 @@ export declare enum Method {
PATCH = "PATCH", PATCH = "PATCH",
DELETE = "DELETE" DELETE = "DELETE"
} }
export declare type Endpoint<R, V, P = void> = Readonly<{ export declare type Endpoint<R, V, P = never> = Readonly<{
method: Method; method: Method;
url: string | ((params: P) => string); url: string | ((params: P) => string);
headers?: Record<string, string>; headers?: Record<string, string>;
transformResponseData?: (data: any) => R; transformResponseData?: (data: any) => R;
}>; }>;
export declare type ExtractEndpointResponse<E> = E extends Endpoint<infer R, any, any> ? R : E extends Endpoint<infer R, any> ? R : never;
export declare type ExtractEndpointVariables<E> = E extends Endpoint<any, infer V, any> ? V : E extends Endpoint<any, infer V> ? V : never;
export declare type ExtractEndpointParams<E> = E extends Endpoint<any, any, infer P> ? P : never;

View file

@ -1,7 +1,7 @@
import { Endpoint } from './endpoint'; import { Endpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables } from './endpoint';
import { PublicRequestState } from './reducer'; import { PublicRequestState } from './reducer';
import { ClientResponse } from './client'; import { ClientResponse } from './client';
export declare type LazyRequestConfig<R, V, P = void> = Readonly<{ export declare type LazyRequestConfig<R, V, P = never> = Readonly<{
variables?: V; variables?: V;
params?: P; params?: P;
headers?: Record<string, string>; headers?: Record<string, string>;
@ -12,4 +12,4 @@ export declare type LazyRequestHandlerConfig<R, V, P> = Readonly<LazyRequestConf
force?: boolean; force?: boolean;
}>; }>;
export declare type RequestHandler<R, V, P> = (config?: LazyRequestHandlerConfig<R, V, P>) => Promise<R | null>; export declare type RequestHandler<R, V, P> = (config?: LazyRequestHandlerConfig<R, V, P>) => Promise<R | null>;
export declare function useLazyRequest<R = Record<string, any>, V = Record<string, any>, P = void>(endpoint: Endpoint<R, V, P>, config?: LazyRequestConfig<R, V, P>): [RequestHandler<R, V, P>, PublicRequestState<R>]; export declare function useLazyRequest<E extends Endpoint<R, V, P>, R = ExtractEndpointResponse<E>, V = ExtractEndpointVariables<E>, P = ExtractEndpointParams<E>>(endpoint: E, config?: LazyRequestConfig<R, V, P>): [RequestHandler<R, V, P>, PublicRequestState<R>];

View file

@ -1,9 +1,9 @@
import { Endpoint } from './endpoint'; import { Endpoint, ExtractEndpointParams, ExtractEndpointResponse, ExtractEndpointVariables } from './endpoint';
import { LazyRequestConfig } from './lazy-request-hook'; import { LazyRequestConfig } from './lazy-request-hook';
export declare type RequestConfig<R, V, P> = Readonly<LazyRequestConfig<R, V, P> & { export declare type RequestConfig<R, V, P> = Readonly<LazyRequestConfig<R, V, P> & {
skip?: boolean; skip?: boolean;
}>; }>;
export declare function useRequest<R = Record<string, any>, V = Record<string, any>, P = void>(endpoint: Endpoint<R, V, P>, config?: RequestConfig<R, V, P>): Pick<Readonly<{ export declare function useRequest<E extends Endpoint<R, V, P>, R = ExtractEndpointResponse<E>, V = ExtractEndpointVariables<E>, P = ExtractEndpointParams<E>>(endpoint: E, config?: RequestConfig<R, V, P>): Pick<Readonly<{
data: R | null; data: R | null;
loading: boolean; loading: boolean;
isCalled: boolean; isCalled: boolean;