React: Analyze Checkout Step Page by a Simple Example
Analyze Checkout Step Page by a Simple Example
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
.
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.