Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Triggering a form within a sheet that’s embedded in form on a different sheet #5768

Open
2 tasks done
ChoaibMouhrach opened this issue Nov 8, 2024 · 2 comments
Open
2 tasks done
Labels
bug Something isn't working

Comments

@ChoaibMouhrach
Copy link

Describe the bug

I have a sheet with a form, and another sheet with its own form. Submitting the form on the second sheet triggers the form on the first sheet.

Create product

"use client";

import { UnitsInput } from "@/client/components/custom/units-input";
import { Button } from "@/client/components/ui/button";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/client/components/ui/form";
import { Input } from "@/client/components/ui/input";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/client/components/ui/sheet";
import { Textarea } from "@/client/components/ui/textarea";
import { SOMETHING_WENT_WRONG } from "@/common/constants";
import { createProductSchema } from "@/common/validations";
import { createProductAction } from "@/server/actions/product";
import { zodResolver } from "@hookform/resolvers/zod";
import { useAction } from "next-safe-action/hooks";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";

const schema = createProductSchema;
type Payload = z.infer<typeof schema>;

export const Create = () => {
  const router = useRouter();

  const form = useForm<Payload>({
    resolver: zodResolver(schema),
    values: {
      name: "",
      unitId: "",
      price: 1,
      shortDescription: "",
      description: "",
    },
  });

  const createProduct = useAction(createProductAction, {
    onSuccess: () => {
      toast.success("Product created successfully");
      form.reset();
      router.refresh();
    },
    onError: ({ error }) => {
      toast.error(error.serverError || SOMETHING_WENT_WRONG);
    },
  });

  const onSubmit = (payload: Payload) => {
    createProduct.execute(payload);
  };

  return (
    <Sheet>
      <SheetTrigger asChild>
        <Button size="sm" variant="outline">
          New product
        </Button>
      </SheetTrigger>
      <SheetContent className="flex flex-col overflow-y-auto">
        <SheetHeader>
          <SheetTitle>New product</SheetTitle>
          <SheetDescription>
            You can add new products from here.
          </SheetDescription>
        </SheetHeader>
        <Form {...form}>
          <form
            className="flex flex-col gap-4"
            onSubmit={form.handleSubmit(onSubmit)}
          >
            <FormField
              name="name"
              control={form.control}
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Name</FormLabel>
                  <FormControl>
                    <Input {...field} placeholder="Fearux" />
                  </FormControl>
                  <FormDescription>The name of the product.</FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            <FormField
              name="unitId"
              control={form.control}
              render={() => (
                <FormItem>
                  <FormLabel>UnitId</FormLabel>
                  <FormControl>
                    <UnitsInput />
                  </FormControl>
                  <FormDescription>The unit of the product.</FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            <FormField
              name="price"
              control={form.control}
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Price</FormLabel>
                  <FormControl>
                    <Input
                      {...field}
                      onChange={(e) =>
                        field.onChange(parseFloat(e.target.value))
                      }
                      placeholder="Fearux"
                      type="number"
                      min="0.001"
                      step="0.001"
                    />
                  </FormControl>
                  <FormDescription>The price of the product.</FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            <FormField
              name="shortDescription"
              control={form.control}
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Short description</FormLabel>
                  <FormControl>
                    <Input {...field} placeholder="This product is..." />
                  </FormControl>
                  <FormDescription>
                    The short description of the product.
                  </FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            <FormField
              name="description"
              control={form.control}
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Description</FormLabel>
                  <FormControl>
                    <Textarea
                      {...field}
                      placeholder="This product is..."
                      rows={8}
                    />
                  </FormControl>
                  <FormDescription>
                    The description of the product.
                  </FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            <div>
              <Button type="submit" pending={createProduct.isPending}>
                Add
              </Button>
            </div>
          </form>
        </Form>
      </SheetContent>
    </Sheet>
  );
};

Units input

import { Plus } from "lucide-react";
import { Button } from "../ui/button";
import { Combobox } from "../ui/combobox";
import { UnitsCreate } from "./units-create";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { getUnitsAction } from "@/server/actions/unit";
import { CustomError } from "@/server/lib/action";
import { SOMETHING_WENT_WRONG } from "@/common/constants";
import { Skeleton } from "../ui/skeleton";
import debounce from "debounce";

export const UnitsInput = () => {
  const [query, setQuery] = useState("");

  const unitsQuery = useQuery({
    queryKey: ["units", query],
    placeholderData: (phd) => phd,
    queryFn: async () => {
      const response = await getUnitsAction({
        query,
        page: 1,
      });

      if (!response?.data) {
        throw new CustomError(response?.serverError || SOMETHING_WENT_WRONG);
      }

      return response.data.data;
    },
  });

  const onQuery = debounce(setQuery, 300);

  if (unitsQuery.isSuccess) {
    return (
      <div className="flex items-center gap-2">
        <Combobox
          onQuery={onQuery}
          className="flex-1"
          items={unitsQuery.data.map((unit) => ({
            label: unit.name,
            value: unit.id,
          }))}
        />
        <UnitsCreate refetch={() => unitsQuery.refetch()}>
          <Button size="icon" variant="outline" className="shrink-0">
            <Plus className="w-4 h-4" />
          </Button>
        </UnitsCreate>
      </div>
    );
  }

  return <Skeleton className="h-9" />;
};

Create unit

"use client";

import { Button } from "@/client/components/ui/button";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/client/components/ui/form";
import { Input } from "@/client/components/ui/input";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/client/components/ui/sheet";
import { SOMETHING_WENT_WRONG } from "@/common/constants";
import { createUnitSchema } from "@/common/validations";
import { createUnitAction } from "@/server/actions/unit";
import { zodResolver } from "@hookform/resolvers/zod";
import { useAction } from "next-safe-action/hooks";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";

const schema = createUnitSchema;
type Payload = z.infer<typeof schema>;

interface UnitsCreateProps {
  children: React.ReactNode;
  refetch?: () => void;
}

export const UnitsCreate: React.FC<UnitsCreateProps> = ({
  children,
  refetch,
}) => {
  const router = useRouter();

  const form = useForm<Payload>({
    resolver: zodResolver(schema),
    values: {
      name: "",
    },
  });

  const createUnit = useAction(createUnitAction, {
    onSuccess: () => {
      toast.success("Unit created successfully");
      (refetch || router.refresh)();
      form.reset();
    },
    onError: ({ error }) => {
      toast.error(error.serverError || SOMETHING_WENT_WRONG);
    },
  });

  const onSubmit = (payload: Payload) => {
    createUnit.execute(payload);
  };

  return (
    <Sheet>
      <SheetTrigger asChild>{children}</SheetTrigger>
      <SheetContent className="flex flex-col" side="left">
        <SheetHeader>
          <SheetTitle>New unit</SheetTitle>
          <SheetDescription>You can add new units from here.</SheetDescription>
        </SheetHeader>
        <Form {...form}>
          <form
            className="flex flex-col gap-4"
            onSubmit={form.handleSubmit(onSubmit)}
          >
            <FormField
              name="name"
              control={form.control}
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Name</FormLabel>
                  <FormControl>
                    <Input {...field} placeholder="kg" />
                  </FormControl>
                  <FormDescription>The name of the unit.</FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />

            <div>
              <Button type="submit" pending={createUnit.isPending}>
                Add
              </Button>
            </div>
          </form>
        </Form>
      </SheetContent>
    </Sheet>
  );
};
Screen.Recording.2024-11-08.at.09.48.06.1.1.1.mov

Affected component/components

sheet, form, button

How to reproduce

render a sheet with a form inside and inside that form render another sheet with another form inside and try submiting the second form and see that the first one triggers

Codesandbox/StackBlitz link

No response

Logs

No response

System Info

MBP m3 pro
sequoia 15.1
Google chrome Version 130.0.6723.92 (Official Build) (arm64)

Before submitting

  • I've made research efforts and searched the documentation
  • I've searched for existing issues
@ChoaibMouhrach ChoaibMouhrach added the bug Something isn't working label Nov 8, 2024
@Sparticuz
Copy link

Also having issues with this. I don't think it's related to it being in a sheet because mine are in dialogs, but they forms are still 'nested' even though both the sheet component and dialog component uses portal to attach them to document.body. I'm not sure what the reason is.

@Sparticuz
Copy link

I'm wondering if this is related. radix-ui/primitives#3199

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants