React Custom Hooks - useFetch

As we know, hooks are functions which you can use with functional components only.

Hooks allows us to reuse the functionality. Consider hooks as a reusable functions or functionalities.

A custom hook needs to be prefixed with use, similar to every other Hook.

Today we will create a custom hooks called as useFetch. This hook will take care of all API calls and will return a response.

You can create a custom hook in a separate file and export it either as a default or a named export.

useFetch


    //custom hook must have use prefix
    export const useFetch = (url, actionObject, dispatch) => {
    useEffect(() => {
        console.log("useEffect of useFetch.. making HTTTP call..");
        fetch(url)
        .then((response) => response.json())
        .then((data) => {
            if (data) {
            actionObject.payload = data;
            dispatch(actionObject);
            }
        });
    }, [url]);
  };

Above hook will call the specified url, capture the response and call the dispatch method with action object after setting the payload.

Notice how the useEffect has dependency on the url. So if same url is requested again, useEffect won't make an HTTP request.

We will use above hook to make HTTP calls in our Fetch call. We will extend our earlier example of useReducer as well.

We need to import this hook into our file in case its in another file. We have removed our useEffect where we were making http call earlier. We have replaced it with the useFetch like below:


  useFetch(state.url, { type: "UPDATE_BOOKS", payload: [] }, dispatch);

Rest of the code remains same, there is no change apart from this.


  import { useFetch } from "./useFetch";

  const FetchAPIDemo = () => {
    // Component State Object
    const defaultState = {
      url: "http://localhost:80/books.json",
      books: [],
    };

    const [clickCount, setClickCount] = useState(0);

    // reducer function
    const reducer = (prevState, action) => {
      //define actions here using switch-case
      switch (action.type) {
        case "UPDATE_BOOKS": {
          return {
            ...prevState,
            books: action.payload,
          };
        }
        case "CHANGE_URL": {
          return {
            ...prevState,
            url: action.payload.newURL,
          };
        }
        default:
          //its good practice to throw error as a default case
          throw new Error(
            `Could not find the dispatched action ==> ${action.type} <==`
          );
      }
    };

    // reducer function with default state value associated with it
    const [state, dispatch] = useReducer(reducer, defaultState);

    useFetch(state.url, { type: "UPDATE_BOOKS", payload: [] }, dispatch);

    const changeAPIUrl = () => {
      // setUrl(newURL) is replaced by dispatch
      dispatch({
        type: "CHANGE_URL",
        payload: { newURL: "http://YOUR_IP_ADDRESS:80/books.json" },
      });
    };

    return (
      <React.Fragment>
        <div className="App">
          <header className="App-header">
            <p>Current counter value is {clickCount}</p>
            {state.books.length > 0 ? (
              <div className="add-column-margin">
                <div
                  id="myCarousel"
                  className="carousel slide"
                  data-ride="carousel"
                >
                  <ol className="carousel-indicators">
                    {state.books.map((book, index) => {
                      return (
                        <li
                          key={index}
                          data-target="#myCarousel"
                          data-slide-to={index}
                          className={index === 0 ? "active" : ""}
                        ></li>
                      );
                    })}
                  </ol>
                  <div className="carousel-inner">
                    {state.books.map((book, index) => {
                      return (
                        <div
                          key={index}
                          className={
                            index === 0 ? "carousel-item active" : "carousel-item"
                          }
                        >
                          <img
                            className="d-block w-100"
                            src={book.coverImage}
                            alt="First slide"
                          ></img>
                          <div className="carousel-caption d-none d-md-block">
                            <h5
                              style={{
                                color: "white",
                                backgroundColor: "rgba(0, 0, 0, 0.6)",
                              }}
                            >
                              {book.title}
                            </h5>
                          </div>
                        </div>
                      );
                    })}
                  </div>
                  <a
                    className="carousel-control-prev"
                    href="#myCarousel"
                    role="button"
                    data-slide="prev"
                  >
                    <span
                      className="carousel-control-prev-icon"
                      aria-hidden="true"
                    ></span>
                    <span className="sr-only">Previous</span>
                  </a>
                  <a
                    className="carousel-control-next"
                    href="#myCarousel"
                    role="button"
                    data-slide="next"
                  >
                    <span
                      className="carousel-control-next-icon"
                      aria-hidden="true"
                    ></span>
                    <span className="sr-only">Next</span>
                  </a>
                </div>
                <button
                  className="btn btn-primary mx-auto float-left"
                  onClick={() => {
                    setClickCount((prevClickCount) => prevClickCount + 1);
                  }}
                >
                  Increment Count
                </button>
                <button
                  className="btn btn-primary mx-auto float-right"
                  onClick={changeAPIUrl}
                >
                  Change URL
                </button>
              </div>
            ) : (
              <p>Loading...</p>
            )}
          </header>
        </div>
      </React.Fragment>
    );
  };

  export default FetchAPIDemo;

Output

When component is rendered and click count changes, we only see useFetch called once as per the console message:








Post click of change url button, useFetch is called again (notice the badge count) below:




Comments

Popular Posts