I have a component where I get answers to a post, and I want the answers to be displayed like an infinite scroll, but when I call the Server Action on the client, I get the error Maximum call stack size exceeded.
The server actions:
export async function getAnswers(params: GetAnswersParams) {
try {
connectToDatabase();
const { questionId, sortBy, page = 1, pageSize = 10 } = params;
const skipAmount = (page - 1) * pageSize;
let sortOptions = {};
switch (sortBy) {
case 'highestUpvotes':
sortOptions = { upvotes: -1 };
break;
case 'lowestUpvotes':
sortOptions = { upvotes: 1 };
break;
case 'recent':
sortOptions = { createdAt: -1 };
break;
case 'old':
sortOptions = { createdAt: 1 };
break;
default:
break;
}
const answers = await Answer.find({ question: questionId })
.populate('author', '_id clerkId name picture')
.sort(sortOptions)
.skip(skipAmount)
.limit(pageSize);
const totalAnswers = await Answer.countDocuments({
question: questionId,
});
const totalPages = Math.ceil(totalAnswers / pageSize);
const isNext = totalAnswers > skipAmount + answers.length;
return { answers, isNext, totalPages };
} catch (error) {
console.log(error);
throw error;
}
}
The server component:
const AllAnswers = async ({ questionId, userId, totalAnswers, page, filter }: Props) => {
const { answers, isNext, totalPages } = await getAnswers({
questionId,
page: page ? +page : 1,
sortBy: filter,
});
return (
<div className='mt-11'>
<div className='flex items-center justify-between'>
<h3 className='primary-text-gradient'>{`${totalAnswers} ${totalAnswers === 1 ? 'Answer' : 'Answers'}`}</h3>
<Filter filters={AnswerFilters} />
</div>
<LoadMoreAnswers
initialAnswers={JSON.stringify(answers)}
questionId={JSON.stringify(questionId)}
userId={userId}
filter={filter}
/>
</div>
);
};
export default AllAnswers;
The client component:
const LoadMoreAnswers = ({ initialAnswers, questionId, userId, filter }: Props) => {
const [allAnswers, setAllAnswers] = useState(JSON.parse(initialAnswers));
// const { ref, inView } = useInView();
const loadMoreAnswers = async () => {
try {
const newAnswers = await getAnswers({
questionId: JSON.parse(questionId),
page: 2,
sortBy: 'recent',
});
// setAllAnswers((oldAnswers) => [...oldAnswers, ...newAnswers]);
console.log(newAnswers);
} catch (error) {
console.error('Error loading more answers:', error);
}
};
return (
<>
{allAnswers.map((answer) => (
<article key={answer._id} className='light-border border-b py-10'>
<div className='mb-8 flex flex-col-reverse justify-between gap-5 sm:flex-row sm:items-center sm:gap-2'>
<Link
href={`/profile/${answer.author.clerkId}`}
className='flex flex-1 items-start gap-1 sm:items-center'
>
<Image
src={answer.author.picture}
alt='profile picture'
width={18}
height={18}
className='rounded-full object-cover max-sm:mt-0.5'
/>
<div className='flex flex-col sm:flex-row sm:items-center'>
<p className='body-semibold text-dark300_light700'>
{answer.author.name}
</p>
<p className='small-regular text-dark400_light500 mt-0.5 line-clamp-1'>
<span className='mx-0.5 max-sm:hidden'>•</span>
{/* answered {getTimestamp(answer.createdAt)} */}
</p>
</div>
</Link>
<div className='flex justify-end'>
<Votes
type='Answer'
itemId={JSON.stringify(answer._id)}
userId={userId}
upvotes={answer.upvotes.length}
hasupVoted={answer.upvotes.includes(userId)}
downvotes={answer.downvotes.length}
hasdownVoted={answer.downvotes.includes(userId)}
/>
</div>
</div>
<ParseHTML data={answer.content} />
</article>
))}
{/* <div className='mt-11 flex w-full items-center justify-center' ref={ref}> */}
{/* <Icons.Spinner className='size-10 animate-spin' /> */}
<Button onClick={loadMoreAnswers}>Load more</Button>
{/* </div> */}
</>
);
};
export default LoadMoreAnswers;
I tried to do it with a spinner at the bottom, using react-intersection-observer, but now I just added a button to test the fetch and it doesn’t work… I don’t understand why it doesn’t work.
For now, I just want to add page 2 when clicking the button, then I think I can figure out the rest.
2
Answers
This is the correct server action up to this point. Notice the total pages implementation might've been wrong too. The problem was the format I was returning the answers from the server action:
First remark i see in your code is that you call a server action in a client component but the action isn’t declared as server action. Also if you have to await for a server action response in a client component, one way is to use
useTransition
hook.Step 1: Make use you server action file starts with
'use server'
Step 2: in your client component, add this:
You can use the
isLoading
state to show a loading spinner or whatever.