diff --git a/components/product/gallery.tsx b/components/product/gallery.tsx
index 0b03557a5..9290f8ea6 100644
--- a/components/product/gallery.tsx
+++ b/components/product/gallery.tsx
@@ -4,38 +4,39 @@ import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline';
import { GridTileImage } from 'components/grid/tile';
import { createUrl } from 'lib/utils';
import Image from 'next/image';
-import Link from 'next/link';
-import { usePathname, useSearchParams } from 'next/navigation';
+import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import { useOptimistic, useTransition } from 'react';
export function Gallery({ images }: { images: { src: string; altText: string }[] }) {
+ const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const imageSearchParam = searchParams.get('image');
const imageIndex = imageSearchParam ? parseInt(imageSearchParam) : 0;
-
- const nextSearchParams = new URLSearchParams(searchParams.toString());
- const nextImageIndex = imageIndex + 1 < images.length ? imageIndex + 1 : 0;
- nextSearchParams.set('image', nextImageIndex.toString());
- const nextUrl = createUrl(pathname, nextSearchParams);
-
- const previousSearchParams = new URLSearchParams(searchParams.toString());
- const previousImageIndex = imageIndex === 0 ? images.length - 1 : imageIndex - 1;
- previousSearchParams.set('image', previousImageIndex.toString());
- const previousUrl = createUrl(pathname, previousSearchParams);
+ const [optimisticIndex, setOptimisticIndex] = useOptimistic(imageIndex);
+ // eslint-disable-next-line no-unused-vars
+ const [pending, startTransition] = useTransition();
const buttonClassName =
'h-full px-6 transition-all ease-in-out hover:scale-110 hover:text-black dark:hover:text-white flex items-center justify-center';
+ function updateIndex(newIndex: number) {
+ setOptimisticIndex(newIndex);
+ const newSearchParams = new URLSearchParams(searchParams.toString());
+ newSearchParams.set('image', newIndex.toString());
+ router.replace(createUrl(pathname, newSearchParams), { scroll: false });
+ }
+
return (
<>
- {images[imageIndex] && (
+ {images[optimisticIndex] && (
)}
@@ -43,23 +44,29 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
{images.length > 1 ? (
-
{
+ startTransition(() => {
+ updateIndex(optimisticIndex - 1);
+ });
+ }}
className={buttonClassName}
- scroll={false}
>
-
+
-
{
+ startTransition(() => {
+ updateIndex(optimisticIndex + 1);
+ });
+ }}
className={buttonClassName}
- scroll={false}
>
-
+
) : null}
@@ -68,18 +75,18 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
{images.length > 1 ? (
{images.map((image, index) => {
- const isActive = index === imageIndex;
- const imageSearchParams = new URLSearchParams(searchParams.toString());
-
- imageSearchParams.set('image', index.toString());
+ const isActive = index === optimisticIndex;
return (
- {
+ startTransition(() => {
+ updateIndex(index);
+ });
+ }}
>
-
+
);
})}
diff --git a/components/product/variant-selector.tsx b/components/product/variant-selector.tsx
index 3940472e2..bb0376020 100644
--- a/components/product/variant-selector.tsx
+++ b/components/product/variant-selector.tsx
@@ -23,6 +23,10 @@ export function VariantSelector({
const pathname = usePathname();
const searchParams = useSearchParams();
const [optimisticVariants, setOptimsticVariants] = useOptimistic(variants);
+ const [optimisticOptions, setOptimisticOptions] = useOptimistic(
+ new URLSearchParams(searchParams.toString())
+ );
+ // eslint-disable-next-line no-unused-vars
const [pending, startTransition] = useTransition();
const hasNoOptionsOrJustOneOption =
@@ -44,20 +48,11 @@ export function VariantSelector({
return options.map((option) => (
- {pending && Loading...
}
{option.name}
{option.values.map((value) => {
const optionNameLowerCase = option.name.toLowerCase();
- // Base option params on current params so we can preserve any other param state in the url.
- const optionSearchParams = new URLSearchParams(searchParams.toString());
-
- // Update the option params using the current option to reflect how the url *would* change,
- // if the option was clicked.
- optionSearchParams.set(optionNameLowerCase, value);
- const optionUrl = createUrl(pathname, optionSearchParams);
-
// In order to determine if an option is available for sale, we need to:
//
// 1. Filter out all other param state
@@ -67,7 +62,7 @@ export function VariantSelector({
// This is the "magic" that will cross check possible variant combinations and preemptively
// disable combinations that are not available. For example, if the color gray is only available in size medium,
// then all other sizes should be disabled.
- const filtered = Array.from(optionSearchParams.entries()).filter(([key, value]) =>
+ const filtered = Array.from(optimisticOptions.entries()).filter(([key, value]) =>
options.find(
(option) => option.name.toLowerCase() === key && option.values.includes(value)
)
@@ -79,7 +74,7 @@ export function VariantSelector({
);
// The option is active if it's in the url params.
- const isActive = searchParams.get(optionNameLowerCase) === value;
+ const isActive = optimisticOptions.get(optionNameLowerCase) === value;
return (
{
startTransition(() => {
const newOptimisticVariants = optimisticVariants.map((variant) => {
- // Assume every variant has an 'options' array where each option has an 'isActive' property.
const updatedOptions = variant.selectedOptions.map((option) => {
if (option.name.toLowerCase() === optionNameLowerCase) {
- return { ...option, value: value, isActive: true }; // Set active optimistically
+ return { ...option, value: value };
}
return option;
});
@@ -100,7 +94,12 @@ export function VariantSelector({
return { ...variant, selectedOptions: updatedOptions };
});
- setOptimsticVariants(newOptimisticVariants); // Update the state optimistically
+ optimisticOptions.set(optionNameLowerCase, value);
+
+ setOptimsticVariants(newOptimisticVariants);
+ setOptimisticOptions(new URLSearchParams(optimisticOptions.toString()));
+
+ const optionUrl = createUrl(pathname, optimisticOptions);
// Navigate without page reload
router.replace(optionUrl, { scroll: false });