简体   繁体   中英

How to make an HTTP hook reusable on the same component

I have an HTTP hook that can be consumed like this:

const { data, error, isLoading, executeFetch } = useHttp<IArticle[]>('news', []);

In the same component, I want to trigger another API call to POST data and update one of the articles:

const handleChange = (article: IArticle, event: React.ChangeEvent<HTMLInputElement>) => {
        executeFetch(`updateNews?id=${article.id}`, { method: 'post', data: { isRead: event.target.checked }});
    };

    return (
        <>
            <div className={classes.articleListHeader}>
                <h1>Article List</h1>
                <small className={classes.headerSubtitle}>{data.length} Articles</small>
            </div>
            <ul>
                {data.map(article => <Article key={article.id} article={article} handleChange={handleChange}/>)}
            </ul>
        </>
    )

My custom hook to fetch data:

export function useHttp<T>(initUrl: string, initData: T): UseHttp<T> {
    const initOptions: AxiosRequestConfig = { url: initUrl };

    const [options, setOptions] = useState(initOptions);

    const useHttpReducer = createHttpReducer<T>();
    const [state, dispatch] = useReducer(useHttpReducer, {
        isLoading: false,
        error: '',
        data: initData
    });

    useEffect(() => {
        let cancelRequest = false;

        const fetchData = async (cancelRequest: boolean = false) => {
            if (!options.url) return;

            dispatch({ type: API_REQUEST});
            try {
                const responsePromise: AxiosPromise<T> = axios(options);
                const response = await responsePromise;
                if (cancelRequest) return;
                dispatch({ type: API_SUCCESS, payload: response.data });
            } catch (e) {
                console.log("Got error", e);
                dispatch({ type: API_ERROR, payload: e.message });
            }
        };
        fetchData(cancelRequest);

        return () => {
            cancelRequest = true;
        }

    }, [options]);

    const executeFetch = (url: string, options: AxiosRequestConfig = axiosInitialOptions): void => {
        options.url = url;
        setOptions(options);
    };


    return { ...state, executeFetch}

The issue is, when I'm doing something like this, the data replaces to the new response (of the POST request), then my UI crashes (no more article list..)

What's the good practice to manage situations like this when I need to call another API in the same component while keeping the reusability of my HTTP hook?

I simply want to execute a POST request somewhere in the component after my GET one - How I can do it in a reusable way and fix my issue?

You can refactoring your custom hook to receive a callback function. I omitted the part of cancelRequest , if you are using axios you can cancel the request via CancelToken :

export function useHttp<T>(initUrl: string): UseHttp<T> {
   const initOptions: AxiosRequestConfig = { url: initUrl };

   const [options, setOptions] = useState(initOptions);

   const useHttpReducer = createHttpReducer<T>();
   const [state, dispatch] = useReducer(useHttpReducer, {
      isLoading: false,
      error: '',
   });

   const fetchData = async (options, callback) => {
      if (!options.url) return;
      dispatch({ type: API_REQUEST});
      try {
         const responsePromise: AxiosPromise<T> = axios(options);
         const response = await responsePromise;
         dispatch({ type: API_SUCCESS, payload: response.data });
         callback(response.data);
      } catch (e) {
         console.log("Got error", e);
         dispatch({ type: API_ERROR, payload: e.message });
      }
   };

   const executeFetch = (url: string, requestOptions: AxiosRequestConfig = axiosInitialOptions, callback): void => {
      options.url = url;
      fetchData({...options, ...requestOptions}, callback);
   };

   return { ...state, executeFetch}
};

Usage:

const [articles, setArticles]= useState();
const { error, isLoading, executeFetch } = useHttpRequest();


const handleChange = (article: IArticle, event: React.ChangeEvent<HTMLInputElement>) => {
   executeFetch(`updateNews?id=${article.id}`, { method: 'post', data: { isRead: event.target.checked }}, setArticles);
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM