Best way to show loading state when using server action on client #51371
-
I am trying to use a server action outside form in a client component. I want to do something once the server action is complete and also have a loading state meanwhile 'use client'
import { addItem } from '../actions'
function ExampleClientComponent({ id }) {
const someEvent = () => {
...
}
return (
<button
onClick={async () => {
await addItem(id);
someEvent();
}}
>
Add To Cart
</button>
)
} To have a loading state in the above case I'll need to create a new state variable for loading, and I can't use |
Beta Was this translation helpful? Give feedback.
Replies: 8 comments 24 replies
-
Additional state is probably your best bet here, given your use case. Some network libs, such as apollo gql, have loading states built into their requests, so if you use a lib in the async function, check to see if it has some kind of state like that you could use. Also, perhaps use a |
Beta Was this translation helpful? Give feedback.
-
Hey, take a look at:
Here are some references: |
Beta Was this translation helpful? Give feedback.
-
I faced the same issue, I had some code that I needed to run after the server action is completed and show a "pending" state in the meantime. I solved it by triggering the server action with 'use client'
import { useTransition, useEffect } from 'react'
import { serverAction } from './serverAction'
export default myClientComponent(){
let [isPending, startTransition] = useTransition();
useEffect(() => {
if(isPending) return;
// THIS CODE WILL RUN AFTER THE SERVER ACTION
}, [isPending]);
const onSubmit = async (formData: FormData) => {
// RUN SOME VALIDATION HERE
startTransition(() => {
serverAction(formData);
});
}
return (
<form action={onSubmit}>
<input type="text" name="test" />
<button type="submit">{isPending ? 'submitting' : 'submit'}</button>
</form>
)
} |
Beta Was this translation helpful? Give feedback.
-
I built a hook to run server actions asyncronously: /hooks/useServerAction.ts import { useState, useEffect, useTransition, useRef } from 'react';
export function useServerAction(action, onFinished = null) {
const [isPending, startTransition] = useTransition();
const [result, setResult] = useState(null);
const [finished, setFinished] = useState(false);
const resolver = useRef(null);
useEffect(() => {
if (!finished) return;
if(onFinished) onFinished(result);
resolver.current(result);
}, [result, finished]);
const runAction = async (args: unknown) => {
startTransition(async () => {
var data = await action(args);
setResult(data);
setFinished(true);
});
return new Promise((resolve, reject) => {
resolver.current = resolve;
});
};
return [runAction, isPending];
}
it can be used from a client component like this: components/ContactForm.ts import { submitFormAction } from '@/actions/ContactFormAction';
import { useServerAction } from '@/hooks/useServerAction';
export default function ContactForm() {
const [runAction, isRunning] = useServerAction(submitFormAction);
const onSubmit = async (formData) => {
// run some validarion here
var result = await runAction(formData);
// continue running some code after the action completed
};
return (<form action={onSubmit}>
<input type="text" name="name" />
<input type="email" name="email" />
<textarea name="message" />
<button type="submit">{isRunning ? 'submitting...' : 'submit'}</button>
</form>);
} |
Beta Was this translation helpful? Give feedback.
-
Here's our recommendations for loading states with forms! https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#displaying-loading-state |
Beta Was this translation helpful? Give feedback.
-
an improved typescript version that allow flexible amount of params, and types based on the action's function type import { useState, useEffect, useTransition, useRef } from 'react';
export const useServerAction = <P extends any[], R>(
action: (...args: P) => Promise<R>,
onFinished?: (_: R | undefined) => void,
): [(...args: P) => Promise<R | undefined>, boolean] => {
const [isPending, startTransition] = useTransition();
const [result, setResult] = useState<R>();
const [finished, setFinished] = useState(false);
const resolver = useRef<(value?: R | PromiseLike<R>) => void>();
useEffect(() => {
if (!finished) return;
if (onFinished) onFinished(result);
resolver.current?.(result);
}, [result, finished]);
const runAction = async (...args: P): Promise<R | undefined> => {
startTransition(() => {
action(...args).then((data) => {
setResult(data);
setFinished(true);
});
});
return new Promise((resolve) => {
resolver.current = resolve;
});
};
return [runAction, isPending];
}; |
Beta Was this translation helpful? Give feedback.
-
Check this out |
Beta Was this translation helpful? Give feedback.
-
Same as @naftali100 at this comment, but Changes:
import { useEffect, useRef, useState, useTransition } from 'react';
export const useServerAction = <P extends any[], R>(
action: (...args: P) => Promise<R>,
onFinished?: (_: R | undefined) => void,
): [(...args: P) => Promise<R | undefined>, boolean] => {
const [isPending, startTransition] = useTransition();
const [result, setResult] = useState<R>();
const [finished, setFinished] = useState(false);
const resolver = useRef<(value?: R | PromiseLike<R>) => void>();
const onFinishRef = useRef(onFinished);
useEffect(() => {
if (!finished) return;
if (onFinishRef.current) onFinishRef.current(result);
resolver.current?.(result);
}, [result, finished]);
const runAction = async (...args: P): Promise<R | undefined> => {
startTransition(() => {
action(...args).then((data) => {
setResult(data);
setFinished(true);
});
});
return new Promise((resolve) => {
resolver.current = resolve;
});
};
return [runAction, isPending];
}; Use in component: async function deleteDataAction(dataId) {
return deleteMyData(dataId)
} const [deleteData, isDeleting] = useServerAction(deleteDataAction) <button onClick={() => deleteData(dataId)} isLoading={isDeleting}> Delete </button> |
Beta Was this translation helpful? Give feedback.
Here's our recommendations for loading states with forms! https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#displaying-loading-state