Skip to content

Instantly share code, notes, and snippets.

@tinrab
Last active May 5, 2024 18:59
Show Gist options
  • Save tinrab/ef8397b62508a67d852ffb37bcd6d1a3 to your computer and use it in GitHub Desktop.
Save tinrab/ef8397b62508a67d852ffb37bcd6d1a3 to your computer and use it in GitHub Desktop.
Calling server actions in client form components
'use server';
export const signUpAction = apiAction(
signUpFormSchema,
async (data): Promise<{ }> => {
// ...
},
);
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;
}
// 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