Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic type in useReducer for a returned parameter

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.

like image 445
George Cook Avatar asked Mar 28 '19 11:03

George Cook


1 Answers

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;
};
like image 181
Oliver Joseph Ash Avatar answered Sep 29 '22 21:09

Oliver Joseph Ash