Next.js Discord

Discord Forum

Is it possible to use the Suspense component with the server actions ?

Answered
Jersey Wooly posted this in #help-forum
Open in Discord
Jersey WoolyOP
I wanna encapsulate my client component with a Suspense component to lazy load the server action call to fetch the data. The problem is that I can find any good working solution to that
Answered by Ray
like this
export default function Client() {
  const [data, setData] = useState([])

  useEffect(() => {
    const loadmore = async () => {
      const data = await serverAction()
      setData(data)
    }

    loadmore()
  }, [])

}
View full answer

37 Replies

Jersey WoolyOP
How can my WishlistList component use a server action to lazy load fetch data ?
@Jersey Wooly you can use the FormState Hook. With that you can show a loading animation or whatever you want
@B33fb0n3 <@177418644123418625> you can use the FormState Hook. With that you can show a loading animation or whatever you want
Jersey WoolyOP
So its not possible to use the Suspense component with server actions ?
no
but you can still do the behavior that you want
Jersey WoolyOP
The thing I don't understand is that I don't have any form so why use a formState ? I just wanna lazy load the data of my client component
yea, the formState thing is just one of multiple options. Another option should be, to fetch it clientside. For example throught a route handler
what?
Jersey WoolyOP
I thought there was a simpler solution to that
If I were you I would do it through the server actions with the formstate. So like the form action is the server action and the rest stays the same. So you can create a component for the form stuff and then you can reuse it in your app. Just pass throught the props the server actions and it's generalized 👍
The point of using server actions is to avoid useEffect and useState
but you said you want to do that in client component
@Ray but you said you want to do that in client component
Jersey WoolyOP
Yeah bc you can fetch data in client component
if you want to avoid useEffect and useState, why use client component there?
Jersey WoolyOP
there is some other stuff that need to be in a client component, just like button onclick events
and also bc if its not client side the server will load them b4 which is not what I want
@B33fb0n3 how? can you give a code example?
like this
export default function Client() {
  const [data, setData] = useState([])

  useEffect(() => {
    const loadmore = async () => {
      const data = await serverAction()
      setData(data)
    }

    loadmore()
  }, [])

}
Answer
Jersey WoolyOP
I want the page to load asap and load the datas after
@Jersey Wooly I want the page to load asap and load the datas after
why not fetching parallel? Same speed but more data?
if you want to fetch on client side, you will need state
Server Actions = Data Mutation
Server Component = Data Fetching
Client Component = Interactivity
Jersey WoolyOP
I did somthg like that and its working now, I also removed the Suspense component since its useless, Is that what you were talking about @Ray ?
'use client';

import {...}

export interface WishlistListProps {
    getWishlists: () => Promise<TActionObjectResponse<TWishlist[]>>;
    removeWishlist: (wishlistId: string) => Promise<TActionResponse>;
}

export default async function WishlistList({ getWishlists, removeWishlist }: WishlistListProps) {
    const [wishlists, setWishlists] = useState<TWishlist[]>([]);
    const [loading, setLoading] = useState<boolean>(true);

    useEffect(() => {
        const fetchData = async () => {
            const data = (await getWishlists()).data || [];
            setWishlists(data);
            setLoading(false);
        };

        fetchData();
    }, []);

    const deleteWishlist = async (wishlistId: string) => {
        const response = await removeWishlist(wishlistId);

        if (!response.isSuccessful) {
            toast({...});
        } else {
            toast({...});
        }
    };

    return (
        <>
          {!loading ? (
            wishlists && wishlists?.length > 0 ? (
                wishlists.map((wishlist) => (
                    <div key={wishlist.id} className="...">
                        <div className="...">
                            <h5 className="...">{wishlist.name}</h5>
                            {wishlist.description && (
                                <p className="...">{wishlist.description}</p>
                            )}
                        </div>
                        <div className="...">
                            <span>{formatDate(wishlist.updatedAt)}</span>
                        </div>
                    </div>
                ))
            ) : (
                <div className="...">
                    <p className="...">
                        Start creating wishlists now! 🌟
                    </p>
                </div>
            )
        ) : (
            <WishesListLoading />
        )}
        </>
    );
}
Jersey WoolyOP
Ok thanks again @B33fb0n3 and @Ray
appreciate it
Siricid woodwasp
HI, doing the same thing but i keep gettin this error
Error: await is only valid in async functions and the top level bodies of modules
export function UploadFileComponent() {
  const [onChange, setOnChange] = useState<File | File[] | null>(null);
  const [progress, setProgress] = useState<Record<string, number>>({});
  const [url, setUrl] = useState<string | null>(null);

  const onDrop = useCallback((acceptedFiles: File[]) => {
    setOnChange(acceptedFiles);
  }, []);
  const { getRootProps, getInputProps, isDragActive, acceptedFiles } =
    useDropzone({
      onDrop,
    });

  const mutation = useMutation({
    mutationKey: ["fileUpload"],
    mutationFn: async ({ file, url }: { file: File; url: string | null }) => {
      if (!url) {
        throw new Error("No url provided");
      }
      await axios.put(url, file, {
        headers: {
          "Content-Type": file.type,
          "Content-Disposition": `attachment; filename="${file.name}"`,
        },
        onUploadProgress: (progressEvent) => {
          setProgress((prevProgress) => ({
            ...prevProgress,
            [file.name]: Math.round(
              (progressEvent.loaded * 100) / (progressEvent.total ?? 0)
            ),
          }));
        },
      });
      return file;
    },
    onSuccess: (file) => {
      toast(`File ${file?.name} uploaded`, {
        description: `File type: ${file?.type.split("/")[1]}, Size: ${
          file?.size
        }`,
      });
    },
    onError: (file) => {
      toast(`Couldn't upload file ${file?.name}`);
    },
  });

  useEffect(() => {
    const fetchUrl = async () => {
      const { url } = await uploadPresignedUrl();
      setUrl(url);
    };

    fetchUrl();
  }, []);

  const handleUpload = async () => {
    if (onChange) {
      if (Array.isArray(acceptedFiles)) {
        acceptedFiles.forEach((file) => {
          mutation.mutate({ file, url });
        });
      } else {
        mutation.mutate({ file: acceptedFiles, url });
      }
    }
  };
i want to invoke uploadPresignedUrl() every time a mutation is happening