React useReducer Hook - (Part - II)

 As we have seen most of the basics of useReducer in my earlier article, lets continue with code.

As discussed earlier, we will use 2 custom actions, each for updating books and URL. We will continue to use useState for clickCount to show that useState and useReducer can work together!

Reducer


  // 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} <==`
        );
    }
  };


As you can see above, by using spread operation, we are copying previous state first and then updating the specific state values.

By doing this, we are preventing other state values being overwritten.

Dispatch

UPDATE_BOOKS


  useEffect(async () => {
    console.log("Use Effect for Books API call is called..");

    const response = await fetch(state.url);
    const data = await response.json();

    // setBooks(data) is replaced by dispatch so reducer can do that for you!
    dispatch({ type: "UPDATE_BOOKS", payload: data });
  }, [state.url]);

CHANGE URL


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

Notice that for books, its a straightforward assignment but for url, we can use payload to send objects as well.

Entire JS File

Apart from above, there are no other code changes required in our previous example of using useEffect and fetch.


  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);

    useEffect(async () => {
      console.log("Use Effect for Books API call is called..");

      const response = await fetch(state.url);
      const data = await response.json();

      // setBooks(data) is replaced by dispatch so reducer can do that for you!
      dispatch({ type: "UPDATE_BOOKS", payload: data });
    }, [state.url]);

    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

There is no visible difference in the output in comparison to the useEffect and fetch.

When component renders for the first time


















When incrementing the count   













  • When we increment the count, useEffect is not called.

Changing the URL













Invalid dispatch action

Just to show you the demo, alongside UPDATE_BOOKS, I will also dispatch UPDATE_USER action, so reducer will throw custom error as below:


    //invalid action, reducer will throw error
    dispatch({ type: "UPDATE_USER", payload: data });
   



Comments

Popular Posts