Last active
May 5, 2024 18:59
-
-
Save tinrab/ef8397b62508a67d852ffb37bcd6d1a3 to your computer and use it in GitHub Desktop.
Calling server actions in client form components
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use server'; | |
export const signUpAction = apiAction( | |
signUpFormSchema, | |
async (data): Promise<{ }> => { | |
// ... | |
}, | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type ApiAction<TSchema extends z.ZodTypeAny, TResult = void> = ( | |
data: z.infer<TSchema>, | |
) => Promise<ActionResponse<TResult>>; | |
type ApiActionFn<TInput, TResult> = (data: TInput) => Promise<TResult>; | |
type ActionResponse<TResult> = { | |
data?: TResult; | |
message?: string; | |
issues?: ZodIssue[]; | |
// status?: Status; | |
}; | |
export function apiAction<TSchema extends z.ZodTypeAny, TResult = void>( | |
schema: TSchema, | |
actionFn: ApiActionFn<z.infer<TSchema>, TResult>, | |
) { | |
return async (data: unknown): Promise<ActionResponse<TResult>> => { | |
const parsedData = schema.safeParse(data); | |
if (parsedData.error) { | |
return { | |
issues: parsedData.error?.issues, | |
}; | |
} | |
if (!parsedData.success) { | |
return { | |
message: 'Failed to parse data.', | |
}; | |
} | |
try { | |
const result = await actionFn(parsedData.data); | |
return { | |
data: result, | |
}; | |
} catch (error) { | |
if (error instanceof IdentityError) { | |
return { status: error.toStatus() }; | |
} | |
// if (isRedirectError(error)) { | |
// return { | |
// message: 'Redirect', | |
// }; | |
// } | |
console.error(error); | |
} | |
return { | |
message: 'Something went wrong.', | |
}; | |
}; | |
} | |
export async function callApiAction< | |
TSchema extends z.ZodTypeAny, | |
TResult = void, | |
>( | |
action: ApiAction<TSchema, TResult>, | |
data: z.infer<TSchema>, | |
): Promise<TResult> { | |
const result = await action(data); | |
if (result.message) { | |
throw new Error(result.message); | |
} | |
if (result.issues && result.issues.length > 0) { | |
throw new ValidationError(new z.ZodError(result.issues)); | |
} | |
/* | |
if (result.status) { | |
const error = statusToDomainError(result.status); | |
error.cause = result.status; | |
throw error; | |
} | |
*/ | |
return result.data as TResult; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Using with react-hook-form | |
export function SignUpForm(props: SignUpFormProps) { | |
const form = useForm<SignUpFormData>({ | |
// ... | |
}); | |
const [error, setError] = useState<string>(); | |
const handleSubmit = async (data: SignUpFormData) => { | |
try { | |
const result = await callApiAction(signUpAction, data); | |
setError(undefined); | |
// .. | |
} catch (error) { | |
if (error instanceof ValidationError) { | |
// ... | |
} | |
setError('Something went wrong.'); | |
} | |
}; | |
return ( | |
<Form {...form}> | |
<FormContent | |
{...props} | |
id={formId} | |
action={signUpAction} | |
onSubmit={form.handleSubmit(handleSubmit)} | |
> | |
{/* ... */} | |
</FormContent> | |
</Form> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment