Reputation: 847
Building an infinite scroll in next.js, I am trying to call my serverAction to loadMoreData and using async/await with it to wait for the API call and get result.
Getting an error:
"async/await is not yet supported in Client Components, only Server Components."
page.tsx is a server component and calls the same server action one time for initial images.
// page.tsx
const Home = async ({ searchParams }) => {
const searchKeyword =
typeof searchParams.keyword === "string" ? searchParams.keyword : "";
let apiResponse = await getPhotos({ query: searchKeyword });
return (
<main className='p-6 flex justify-center w-full min-h-screen bg-black'>
<InfiniteScrollData
search={searchKeyword}
initialData={apiResponse?.results || []}
/>
</main>
);
};
export default Home;
Here is the server action:
// actions.ts
"use server";
export const getData = async ({
query,
}) => {
const { response } = await fetchData({
query,
});
apiResponse = response;
return response;
};
And following is the client component
// InfiniteScrollData.tsx
"use client";
// imports
const InfiniteScrollImages: = ({
search,
initialData,
}) => {
const [data, setData] = useState(initialData);
const [page, setPage] = useState(1);
const [loaderRef, inView] = useInView();
const loadMoreData = useCallback(async () => {
const next = page + 1;
const response = await getData({ query: search, page: next });
if (response?.results?.length) {
setPage(next);
setData((prev) => [...prev, ...(response?.results || [])]);
}
}, [page, search]);
useEffect(() => {
if (inView) {
loadMoreData();
}
}, [inView, loadMoreData]);
return (
<>
<div>
{photos?.map((item) => (
<Card key={item.id} data={item} />
))}
</div>
<div ref={loaderRef}>
<Loader />
</div>
</>
);
};
export default InfiniteScrollImages;
Using [email protected]
Maybe there is another way of calling server actions from client components?
Upvotes: 0
Views: 2622
Reputation: 49293
there are 2 options to call server actions in client components.
First is using bind
You can bind arguments to a Server Action using the bind method. This allows you to create a new Server Action with some arguments already bound. This is beneficial when you want to pass extra arguments to a Server Action.
Good to know: .bind of a Server Action works in both Server and Client Components. It also supports Progressive Enhancement.
Progressive enhancement allows a to function properly without JavaScript, or with JavaScript disabled.
since you are Calling a Server Action outside of form, u need to use useTransition
When using a Server Action outside of a form, call the Server Action in a transition, which allows you to display a loading indicator, show optimistic state updates, and handle unexpected errors. Forms will automatically wrap Server Actions in transitions.
since we usually navigate the user to somewhere else, startTransition
makes sure we redirect after data is changed.
Upvotes: 1
Reputation: 129
You are mixing client components with server components.
Check this link which will give more idea on this : https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#example
Below code will remove the error.
// page.tsx
const Home = ({ searchParams }) => {
const searchKeyword =
typeof searchParams.keyword === "string" ? searchParams.keyword : "";
return (
<main className='p-6 flex justify-center w-full min-h-screen bg-black'>
<InfiniteScrollData
search={searchKeyword}
/>
</main>
);
};
export default Home;
This component has to load the data. You can't implement infinite loading on server component. That has to be done in client only.
// InfiniteScrollData.tsx
"use client";
// imports
const InfiniteScrollImages: = ({
search,
initialData,
}) => {
let initialData = await getPhotos({ query: searchKeyword });
const [data, setData] = useState(initialData);
const [page, setPage] = useState(1);
const [loaderRef, inView] = useInView();
const loadMoreData = useCallback(async () => {
const next = page + 1;
const response = await getData({ query: search, page: next });
if (response?.results?.length) {
setPage(next);
setData((prev) => [...prev, ...(response?.results || [])]);
}
}, [page, search]);
useEffect(() => {
if (inView) {
loadMoreData();
}
}, [inView, loadMoreData]);
return (
<>
<div>
{photos?.map((item) => (
<Card key={item.id} data={item} />
))}
</div>
<div ref={loaderRef}>
<Loader />
</div>
</>
);
};
export default InfiniteScrollImages;
Upvotes: 0
Reputation: 151
Maybe there is another way of calling server actions from client components?
You have two options:
Turn getData
to client code - This just requires that getData is moved from the actions file without the "use server"
directive and your code should work as expected. But I'm sure you wouldn't want this.
The second is to follow react's docs and call the Server Action in a transition. You would need to modify your loadMoreData
handler as below:
const loadMoreData = useCallback(() => {
startTransition(async () => {
const next = page + 1;
const response = await getData({ query: search, page: next });
if (response?.results?.length) {
setPage(next);
setData((prev) => [...prev, ...(response?.results || [])]);
}
});
}, [page, search]);
More information is available on react's docs on server actions
Upvotes: 1
Reputation: 1572
As the error suggested async await isn't supported in client components. Your alternative would be to handle promises. Your getData async function returns a promise which you can handle in your useCallback or just return the promise there as well and handle the promise in the useEffect
Without a demo I am kind of guessing so here is a rough solution:
const loadMoreData = useCallback(() => {
const next = page + 1;
getData({ query: search, page: next }).then(response => {
if (response?.results?.length) {
setPage(next);
setData((prev) => [...prev, ...(response?.results || [])]);
}
}).error(() => {
});
}, [page, search]);
On Nextjs documentation for client data fetching this is the example they give:
import { useState, useEffect } from 'react'
function Profile() {
const [data, setData] = useState(null)
const [isLoading, setLoading] = useState(true)
useEffect(() => {
fetch('/api/profile-data')
.then((res) => res.json())
.then((data) => {
setData(data)
setLoading(false)
})
}, [])
if (isLoading) return <p>Loading...</p>
if (!data) return <p>No profile data</p>
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
)
}
Upvotes: 0
Reputation: 2161
Your error directly says that you cannot do await
functions in client components, so you should find away to get rid of that in your code:
const Home = ({ searchParams }) => { // no async
Upvotes: -2