The source code for this blog is available on GitHub.
Note
Top

React: Analyze Checkout Step Page by a Simple Example

Cover Image for React: Analyze Checkout Step Page by a Simple Example
Chen Han
Chen Han

Analyze Checkout Step Page by a Simple Example

img

import { useRecoilState } from "recoil";
import Button from "@/components/Button";
import { isEmpty } from "@/util/generic";
import { useRouter } from "next/router";
import { useEffect, ReactNode } from "react";
import { userIdState, userOrderIdsState } from "@/atoms/experimental/user";
import classname from "classnames";

function RecoilLayout({
  step,
  children,
}: {
  step?: string;
  children: ReactNode;
}) {
  return (
    <div className="min-h-screen container mt-6">
      <div className="flex justify-center">
        <div className={classname("mx-3", ["1", "2", "3"].includes(step as string) && "font-bold")}>
          Fill Up User ID
        </div>
        <div className={classname("mx-3", ["2", "3"].includes(step as string) && "font-bold")}>
          Fill Up Order IDs
        </div>
        <div className={classname("mx-3", step === "3" && "font-bold")}>
          Complete
        </div>
      </div>
      {children}
    </div>
  );
}

export default function Recoil() {
  const router = useRouter();
  const step = router.query.step || "1";

  useEffect(() => {
    // redirect to step 1 if no step query parameter is present in the URL
    if (!step) {
      router.push("/experimental/recoil?step=1");
    }
  }, []);

  /* use selector examples */
  const [userId, setUserId] = useRecoilState(userIdState);
  const [orderIds, setOrderIds] = useRecoilState(userOrderIdsState);

  function handleUserId() {
    setUserId(100);
    router.push("/experimental/recoil?step=2");
  }

  function handleUserOrderIds() {
    setOrderIds([1, 2, 3, 4, 5]);
    router.push("/experimental/recoil?step=3");
  }

  switch (step) {
    case "1":
      return (
        <RecoilLayout step={step}>
          <div id="step-1">
            <div>user id: {userId}</div>
            <Button
              small
              text="set user"
              type="primary"
              className="mr-2"
              onClick={handleUserId}
            />
          </div>
        </RecoilLayout>
      );
    case "2":
      if (isEmpty(userId)) {
        router.push("/experimental/recoil?step=1");
      }
      
      return (
        <RecoilLayout step={step}>
          <div id="step-2">
            <div>user's order: {orderIds}</div>
            <Button
              small
              text="set orders"
              type="primary"
              className="mr-2"
              onClick={handleUserOrderIds}
            />
          </div>
        </RecoilLayout>
      );
    case "3":
      if (isEmpty(orderIds)) {
        router.push("/experimental/recoil?step=2");
      }
      
      return (
        <RecoilLayout step={step}>
          <div id="step-3" className="my-6">
            <h6 className="my-2">Complete!</h6>
            <div className="text-left">
              <div>user id: {userId}</div>
              <div>user's orders: {orderIds.join(", ")}</div>
            </div>
          </div>
        </RecoilLayout>
      );
    default:
      return (
        <RecoilLayout step={step as string}>
          {isEmpty(userId) && isEmpty(orderIds) && (
            <div id="step-1">
              <div>user id: {userId}</div>
              <Button
                small
                text="set user"
                type="primary"
                className="mr-2"
                onClick={handleUserId}
              />
            </div>
          )}
          {!isEmpty(userId) && isEmpty(orderIds) && (
            <div id="step-2">
              <div>user's order: {orderIds}</div>
              <Button
                small
                text="set orders"
                type="primary"
                className="mr-2"
                onClick={handleUserOrderIds}
              />
            </div>
          )}

          {!isEmpty(userId) && !isEmpty(orderIds) && (
            <div id="step-3" className="my-6">
              <h6>Complete!</h6>
              <div className="text-left">
                <div>user id: {userId}</div>
                <div>user's orders: {orderIds.join(", ")}</div>
              </div>
            </div>
          )}
        </RecoilLayout>
      );
  }
}

Redirect issue

It is a React functional component that uses the useRouter hook from the next/router package to get the current URL query parameter value for the step key. The useEffect hook is used to check if the step query parameter is present in the URL and redirect to step 1 if it is not.

The component then switches on the step value and renders a different component based on the case. For step 1, it renders the RecoilLayout component with some content. For step 2, it checks if the userId is empty and redirects back to step 1 if it is. If userId is not empty, it renders the RecoilLayout component with some other content. For step 3, it checks if orderIds is empty and redirects back to step 2 if it is. If orderIds is not empty, it renders the RecoilLayout component with some different content.

The issue you mentioned is that when you type the URL manually to step3, it first jumps you to step2 and eventually jumps to step1. This behavior is because the useEffect hook is called when the component is mounted and checks if the step query parameter is present in the URL. If it is not present, it redirects to step1. However, if you manually type the URL with a step query parameter value of 3, the useEffect hook is not called again and the component continues with the switch statement, which redirects to step2 since orderIds is empty, and then redirects to step1 since step query parameter is not present in the URL.

To fix this issue, you can modify the useEffect hook to also check if the step query parameter value is not one of the valid steps (1, 2, or 3) and redirect to step1 in that case as well. This way, even if you manually type the URL with an invalid step value, the useEffect hook will be called again and redirect to step1.

img

export default function Recoil() {
  const router = useRouter();
  const step = router.query.step || "1";

  useEffect(() => {
    // redirect to step 1 if no step query parameter is present in the URL
    if (!step) {
      router.push("/experimental/recoil?step=1");
    }
  }, []);

  switch (step) {
    case "1":
      return (
        <RecoilLayout step={step}>
          ...
        </RecoilLayout>
      );
    case "2":
      if (isEmpty(userId)) {
        router.push("/experimental/recoil?step=1");
      }
      
      return (
        <RecoilLayout step={step}>
          ...
        </RecoilLayout>
      );
    case "3":
      if (isEmpty(orderIds)) {
        router.push("/experimental/recoil?step=2");
      }
      
      return (
        <RecoilLayout step={step}>
          ...
        </RecoilLayout>
      );
    default:
      return (
        <RecoilLayout step={step as string}>
          ...
        </RecoilLayout>
      );
  }
}

Here's the modified code for the useEffect hook:

case "3":
  if (isEmpty(orderIds) && isEmpty(userId)) {
    router.push("/experimental/recoil?step=1");
  } else {
    router.push("/experimental/recoil?step=2");
  }

  return (
    <RecoilLayout step={step}>
      ...
    </RecoilLayout>
  );

Actual Page isn't consistent with Breadcrumb steps

When clicking on the “Proceed to Checkout” button in the add to cart modal to the delivery step in the checkout, the page that is rendered is the confirmation page. The step name seems to be aligned with “delivery” and the url is also indicating that the rendered UI should be delivery but the confirmation breadcrumb is highlighted.

© 2024 WOOTHINK. All Rights Reserved.
Site MapTerms and ConditionsPrivacy PolicyCookie Policy