Search code examples
reactjstypescriptnext.js

Server Action to a Client Component as a prop TS Warning


I am having a TS warning when I pass a server action into my client component. It does not give a compile error, so I could ignore but I'd prefer to get rid of the warning.

TS71007: Props must be serializable for components in the "use client" entry file, "clientStatusFormAction" is invalid.

Here is where my server action is defined:

export default async function EditClientStatus({params}: { params: { id: number } }) {
    const clientStatus = await getClientStatusById(params.id);

     const editClientStatusAction = async (_prevState: any, formData: FormData): Promise<ApiResponse<ClientStatus>> => {
        'use server';

        // do stuff
    }
    return (
        <div>
            <h1>Edit Client Status</h1>
            <ClientStatusForm clientStatus={clientStatus.result} clientStatusFormAction={editClientStatusAction} />
        </div>
    );
}

Here is my client component:

"use client";

// imports removed for brevity

type ClientStatusFormProps = {
    clientStatus: ClientStatus,
    clientStatusFormAction: (_prevState: any, formData: FormData) => Promise<ApiResponse<ClientStatus>>
};


export default function ClientStatusForm({clientStatus, clientStatusFormAction}: ClientStatusFormProps) {
    const [formState, formAction] = useFormState<Promise<ApiResponse<ClientStatus>>, FormData>(clientStatusFormAction, {success: false, error: {errorType: "", messages: []} });

    return (
        <form action={formAction}>
            <div className="flex flex-wrap mb-6">
                <GenericInput name="statusName" type="text" label="Status Name" required={true} isTwoColumn={true} maxLength={124} defaultValue={clientStatus.statusName} />
                <GenericInput name="description" type="text" label="Status Description" required={true} isTwoColumn={true} maxLength={255} defaultValue={clientStatus.description} />
            </div>
            <SubmitButton buttonText="Save" />
        </form>
    );
}

Solution

  • I found a workaround to avoid the lint warning.

    I moved my Server Action function into a separate Typescript file that already had 'use server' directive like so:

    //actions.js
    'use server'
    
    export const editClientStatusFormAction = async (_prevState: ApiResponse<ClientStatus>, formData: FormData): Promise<ApiResponse<ClientStatus>> => {
      //Server action stuff
    }
    
    export type EditClientStatusAction = typeof editClientStatusFormAction;
    

    I exported the type of the function and modified my props type on the client component like so:

    type ClientStatusFormProps = {
        clientStatus: ClientStatus,
        clientStatusFormAction: EditClientStatusAction
    };
    
    
    export default function ClientStatusForm({clientStatus, clientStatusFormAction}: ClientStatusFormProps) {
        const [formState, formAction] = useFormState<Promise<ApiResponse<ClientStatus>>, FormData>(clientStatusFormAction, {success: false, error: {errorType: "", messages: []} });
    
        return (
            <form action={formAction}>
                <div className="flex flex-wrap mb-6">
                    <GenericInput name="statusName" type="text" label="Status Name" required={true} isTwoColumn={true} maxLength={124} defaultValue={clientStatus.statusName} />
                    <GenericInput name="description" type="text" label="Status Description" required={true} isTwoColumn={true} maxLength={255} defaultValue={clientStatus.description} />
                </div>
                <SubmitButton buttonText="Save" />
            </form>
        );
    }