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

[ASK] How do I avoid FetchError: "TypeError: Cannot redefine property: $$id" #56

Closed
1 task done
John-Dennehy opened this issue Jan 24, 2024 · 7 comments
Closed
1 task done
Labels
question Further information is requested

Comments

@John-Dennehy
Copy link

Is there an existing issue for this?

  • I have searched the existing issues and found nothing that matches

Library version (optional)

6.0.2

Ask a question

I assume I have made a silly error, but I keep getting a "Fetch Error" when running execute from useAction hook.

Everything works fine if I run the db query insertGroup(values) instead of execute, but the minute I try and use useAction I get "TypeError: Cannot redefine property: $$id"

I'm following developedbyed s tutorial, if that helps: https://www.youtube.com/watch?v=UKupfEuUc1M&t=71s

// safe-server-actions-client.ts

import { createSafeActionClient } from "next-safe-action"
export const serverActionClient = createSafeActionClient()
export default serverActionClient
/// create-group-action.ts
"use server"

import { insertGroupSchema } from "@/server/data/schema"
import { revalidatePath } from "next/cache"
import { insertGroup } from "../queries/groups"
import { serverActionClient } from "./utils/safe-sever-actions-client"

type ActionSuccess = { success: true; status: "success" }
type ActionError = { success: false; status: "error"; errorMessage: string }
type ActionResponse = ActionSuccess | ActionError

export const createGroupAction = serverActionClient(insertGroupSchema, async (formData): Promise<ActionResponse> => {
  if (!formData) return { success: false, status: "error", errorMessage: "No form data" }

  await insertGroup(formData)
  revalidatePath("/")
  revalidatePath("/groups")
  return { success: true, status: "success" }
})

export default createGroupAction
// 
"use client"

import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"
import { groupsTable, insertGroupSchema } from "@/server/data/schema"
import { DevTool } from "@hookform/devtools"
import { zodResolver } from "@hookform/resolvers/zod"
import { Table } from "drizzle-orm"
import { ControllerRenderProps, useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "../ui/button"
import { Checkbox } from "../ui/checkbox"
import { toast } from "sonner"
import createGroupAction from "@/server/actions/create-group-action"
import { useAction } from "next-safe-action/hooks"
import { insertGroup } from "@/server/queries/groups"

import { FormMap } from "@/app/form-hooks"
import { Input } from "../ui/input"
import { Textarea } from "../ui/textarea"

export const newGroupFormMap: FormMap = {
  fields: [
    {
      name: "name",
      label: "Group Name",
      description: "This is the name of the group.",
      Component: Input,
    },
    {
      name: "description",
      label: "Group Description",
      description: "This is the description of the group.",
      Component: Textarea,
      componentProps: { placeholder: "GroupDescription" },
    },
    {
      name: "logoUrl",
      label: "Group Logo URL",
      description: "This is the URL of the group logo, including the prefix (for example https://)",
      Component: Input,
      componentProps: { placeholder: "https://example.com/logo.png" },
    },
    // {
    //   name: "active",
    //   label: "Group Active",
    //   description: "This is whether the group is active.",
    //   Component: Checkbox,
    //   componentProps: {},
    // },
  ],
} as const

const defaultValidationSchema = insertGroupSchema.pick({
  name: true,
  description: true,
  active: true,
})

type NewGroupFormProps = {
  tableSchema?: Table
  validationSchema?: any
  formMap?: typeof newGroupFormMap
}
export default function NewGroupForm({
  tableSchema = groupsTable,
  validationSchema = defaultValidationSchema,
  formMap = newGroupFormMap,
}: NewGroupFormProps) {
  const { execute, result, status } = useAction(createGroupAction, {
    onSuccess: (_data, { name }) => {
      toast.success(`Success: Group ${name} created`)
    },
    onError: (error, input) => {
      if (error.fetchError) toast.error(`Fetch Error: ${error.fetchError}. Failed to create ${input.name}`)
      if (error.serverError) {
        console.error(`${error.serverError}`)
        toast.error(`Server Error: Failed to create ${input.name}`)
      }
      if (error.validationErrors) toast.error(`Validation errors: ${error.validationErrors}`)
    },
  })
  const form = useForm<z.input<typeof validationSchema>>({
    resolver: zodResolver(validationSchema),
  })

  async function handleValid(values: z.output<typeof validationSchema>) {
    execute(values)
  }

  const handleInvalid = () => {
    // TODO: handle form submission error
    toast.error("Group creation failed")
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(handleValid, handleInvalid)} className="flex flex-col gap-4">
        {formMap.fields.map((formField) => {
          const { Component, componentProps, name, label, description } = formField

          const getProps = (field: ControllerRenderProps) => {
            let props = { ...componentProps }

            //	add additional props that require `field` value for Checkbox
            if (Component === Checkbox)
              props = {
                ...componentProps,
                checked: field.value,
                onCheckedChange: field.onChange,
              }

            return { ...field, ...props }
          }

          return (
            <FormField
              key={name}
              control={form.control}
              name={name}
              render={({ field }) => (
                <FormItem>
                  <FormLabel>{label}</FormLabel>
                  <FormControl>
                    <Component {...getProps(field)} />
                  </FormControl>
                  <FormDescription>{description}</FormDescription>
                  <FormMessage />
                </FormItem>
              )}
            />
          )
        })}
        <Button aria-disabled={status === "executing"} type="submit">
          Submit
        </Button>
      </form>
      {/* <DevTool control={form.control} /> */}
    </Form>
  )
}

Additional context

No response

@John-Dennehy John-Dennehy added the question Further information is requested label Jan 24, 2024
@John-Dennehy
Copy link
Author

John-Dennehy commented Jan 24, 2024

The more I try and investigate this I think it has something to do with how actions are being compiled by turbopack. Specifically this function in my .next/server/chunks/ folder:

function createActionProxy(id, action) {
    return (0, _serveredge.registerServerReference)(action, id, null);
} //# sourceMappingURL=action-proxy.js.map

EDIT: Get the same error without --turbopack

@TheEdoRan
Copy link
Owner

Well, that's pretty unusual. I don't use Drizzle but it should not happen, never happened to me using Prisma/Kysely. Can you please provide a minimal reproduction example in a repo, so I can investigate this issue? Thank you.

@John-Dennehy
Copy link
Author

Annoyingly, so far I have been unable to recreate the issue in a new repo. As I add complexity, I'll let you know if it crops up again.

@John-Dennehy
Copy link
Author

I can't believe what seems to have actually caused this issue!!

Works fine

import GroupList from "@/components/group-list";
import { db } from "@/server/db";
import groupsTable from "@/server/db/schema";

export async function HomePage() {
  const groups = await db.select().from(groupsTable);

  return (
    <main className="container mx-auto">
      <div className="flex flex-col gap-4">
        <h1>Home</h1>
        <GroupList groups={groups} />
      </div>
    </main>
  );
}

export default HomePage;

Also works fine

"use server";

import GroupList from "@/components/group-list";
import { db } from "@/server/db";
import groupsTable from "@/server/db/schema";

export default async function HomePage() {
  const groups = await db.select().from(groupsTable);

  return (
    <main className="container mx-auto">
      <div className="flex flex-col gap-4">
        <h1>Home</h1>
        <GroupList groups={groups} />
      </div>
    </main>
  );
}

BUT - using "use server" AND having the default export declared at the end, causes the error. This code causes the error:

"use server";

import GroupList from "@/components/group-list";
import { db } from "@/server/db";
import groupsTable from "@/server/db/schema";

export async function HomePage() {
  const groups = await db.select().from(groupsTable);

  return (
    <main className="container mx-auto">
      <div className="flex flex-col gap-4">
        <h1>Home</h1>
        <GroupList groups={groups} />
      </div>
    </main>
  );
}

export default HomePage;

@TheEdoRan
Copy link
Owner

Thank you for your tests! So this is a Next.js issue, right? Can we close this one and track this PR?

@John-Dennehy
Copy link
Author

John-Dennehy commented Jan 28, 2024

Agree this is likely an issue for Next.js to fix in vercel/next.js#54655

Useful one to be aware of though! Whod have thought exporting named and default would cause such issues?

EDIT: Looks like a fix already merged into canary: vercel/next.js#61244

@TheEdoRan
Copy link
Owner

Whod have thought exporting named and default would cause such issues?

Every day we find something new in this amazing Server Actions world! 😅

Thank you again for your work, I'll close this issue since it's a framework related one, and probably fixed in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants