Search code examples
javascriptreactjstypescriptreact-hooksuse-effect

react hook useEffect to fetch data on button click (typescript)


I have a component and I want to fetch isbn data on button click using react hook useEffect, performing a get on the route ${basicUrl}/editorials/${isbn}, so i wrote this component:

import React, { Fragment, useState } from "react";
import "./Home.css";
import { V3_BASIC_URL } from "../../constants/endpoints";
import { useDataApi } from "../../store/effects/dataEffects";

import SearchIsbnElement from "../../components/SearchIsbnElement/SearchIsbnElement";
import IsbnPanelElement from "../../components/IsbnPanelElement/IsbnPanelElement";

function Home() {
  const [query, setQuery] = useState<string>("9788808677853");
  const [isValid, setIsValid] = useState<boolean>(true);

  const url = `${V3_BASIC_URL(
    process.env.REACT_APP_API_ENV
  )}/editorials/${query}`;
  const [{ isbn, isLoading, isError }, doFetch] = useDataApi(url, {
    isLoading: false,
    isError: false,
    isbn: undefined,
  });

  const buttonCallback = () => {
    doFetch(url);
  };
  const isbnRegexp = /^97\d{11}$/
  const validateQuery = (query: string): boolean => isbnRegexp.test(query)

  const inputCallback = (query: string) => {
    setQuery(query)
    setIsValid(validateQuery(query));
  };

  return (
    <div id="isbn-panel-home" className="Home">
      <SearchIsbnElement
        inputCallback={inputCallback}
        buttonCallback={buttonCallback}
        query={query}
        isValid={isValid}
      ></SearchIsbnElement>
      {isError && <div>Il servizio al momento non è disponibile, riprova più tardi</div>}
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        !isError && 
        <Fragment>
          <IsbnPanelElement isbn={isbn}></IsbnPanelElement> 
          <p>{isbn?.scheda_volume == null && 'isbn non trovato'}</p>
        </Fragment>
      )}
    </div>
  );
}

export default Home;

the useDataApi function uses the hook useEffect and returns state and setUrl action to set the new url on isbn value change. This is the useDataApi file:

import { useState, useEffect, useReducer } from "react";

import {
  dataFetchFailure,
  dataFetchInit,
  dataFetchSuccess,
} from "../actions/dataActions";
import { dataFetchReducer, ISBNState } from "../reducers/dataReducers";
import { get } from "../../tools/request";

type InitialState = {
  isLoading: boolean,
  isError: boolean,
  isbn: undefined,
}

export const useDataApi = (initialUrl: string, initialData: InitialState) : [ISBNState, (value: string) => void]  => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, initialData);

  useEffect(() => {
    let didCancel: boolean = false;

    const fetchData = async (): Promise<any> => {
      dispatch(dataFetchInit());
      const options = {
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json'
        },
        auth: {
          username: `${process.env.REACT_APP_API_AUTH_USER}`,
          password: `${process.env.REACT_APP_API_AUTH_PWD}`
        }
      }
      try {
        const {data} = await get(url, options);
        if (!didCancel) {
          dispatch(dataFetchSuccess(data));
        }
      } catch (error) {
        if (!didCancel) {
          dispatch(dataFetchFailure(error));
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  return [state, setUrl];
};

with this code fetching starts on page load, but i want to fetch data only on button click. How can I do this?


Solution

  • useEffect() is a hook to manipulate the component through the different lifecycle methods. In order to do something onClick you need to create a method for that:

    const fetchData = async (): Promise<any> => {
      dispatch(dataFetchInit());
      const options = {
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json'
        },
        auth: {
          username: `${process.env.REACT_APP_API_AUTH_USER}`,
          password: `${process.env.REACT_APP_API_AUTH_PWD}`
        }
      }
      try {
        const {data} = await get(url, options);
        if (!didCancel) {
          dispatch(dataFetchSuccess(data));
        }
      } catch (error) {
        if (!didCancel) {
          dispatch(dataFetchFailure(error));
        }
      }
    };
    

    Just do that and you will be fine

    Edit: the new version of useDataApi

    export const useDataApi = (
      url: string,
      initialData: InitialState
    ): [ISBNState, (value: string) => void] => {
      const [state, dispatch] = useReducer(dataFetchReducer, initialData);
    
      const fetchData = useCallback(async (): Promise<any> => {
        dispatch(dataFetchInit());
        const options = {
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
          },
          auth: {
            username: `${process.env.REACT_APP_API_AUTH_USER}`,
            password: `${process.env.REACT_APP_API_AUTH_PWD}`,
          },
        };
        try {
          const { data } = await get(url, options);
          dispatch(dataFetchSuccess(data));
        } catch (error) {
          dispatch(dataFetchFailure(error));
        }
      }, [url]);
    
      return [state, fetchData];
    };