epicweb-dev / epic-stack

This is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.
https://www.epicweb.dev/epic-stack
MIT License
4.9k stars 397 forks source link

Error on onboarding.tsx when the validation fails, can't create the account #466

Closed luisiacc closed 1 year ago

luisiacc commented 1 year ago

Description:

When on the onboarding view, filling out the username, name, password, if the user makes a mistake wether it is that passwords don't match, or don't check the TOS checkbox, after they correct those errors and try to create the account, there is an error on the username, and after changing the username there is a 50 error when creating the session because it says the email already exists.

This is reproducible both locally and on production.

Steps to reproduce:

1- Go to create an account view 2- Put email 3- Put code from email 4- Fill out username, name, password and password confirm, don't check the TOS, make the paswords and password confirms different, ex:

password: 123qweasd
password_confirm: 123qweasd.

5- Hit submit, you get an error for the TOS 6- Check the TOS and submit, get an error for the password 7- Fix the passwords, get an error from the username 8- Fix the username, get an error from the server, "unique constraint" when creating the user.

Why do I think this is happening? When you get the errors, and then go back to fix them, looks like conform sends some submissions to the action with intents like validate/password or validate/theFieldThatHaveTheTosIDontRemember, and because the session is created on the .transform() while parsing the request data, before determinating if the submission.intent is submit, the session is being created on that moment.

...
    const submission = await parse(formData, {
        schema: SignupFormSchema.superRefine(async (data, ctx) => {
            const existingUser = await prisma.user.findUnique({
                where: { username: data.username },
                select: { id: true },
            })
            if (existingUser) {
                ctx.addIssue({
                    path: ['username'],
                    code: z.ZodIssueCode.custom,
                    message: 'A user already exists with this username',
                })
                return
            }
        }).transform(async data => {
            const session = await signup({ ...data, email })
            return { ...data, session }
        }),
        async: true,
    })

    if (submission.intent !== 'submit') {
        return json({ status: 'idle', submission } as const)
    }
    if (!submission.value?.session) {
        return json({ status: 'error', submission } as const, { status: 400 })
    }
...

How did I solved it?

I fixed this on my code by removing the transform() that creates the session, and only creating the session after the validation and the submission.intent !== 'submit' check, there is probably a cleaner way to do it.

...
    const submission = await parse(formData, {
        schema: SignupFormSchema.superRefine(async (data, ctx) => {
            const existingUser = await prisma.user.findUnique({
                where: { username: data.username },
                select: { id: true },
            })
            if (existingUser) {
                ctx.addIssue({
                    path: ['username'],
                    code: z.ZodIssueCode.custom,
                    message: 'A user already exists with this username',
                })
                return
            }
        }),
        async: true,
    })

    if (submission.intent !== 'submit') {
        return json({ status: 'idle', submission } as const)
    }

    let hasError = Object.keys(submission.error).length > 0
    if (hasError || !submission.value) {
        return json({ status: 'error', submission } as const, { status: 400 })
    }

    let { remember, redirectTo, username, password, name } = submission.value
    const session = await signup({ email, username, password, name })
...
kentcdodds commented 1 year ago

I think I've fixed this (though I couldn't reproduce it). I made a video explaining what's probably going on: https://www.epicweb.dev/tips/fixing-a-form-validation-bug-in-the-epic-stack

Here's the commit that should fix it: https://github.com/epicweb-dev/epic-stack/commit/4559ae734ee2df10cc3cee8fbac543240740da39

Please let me know if that fixes it for you!

luisiacc commented 1 year ago

Hi @kentcdodds, yes, this fixed the problem!