Reputation: 6961
I wanna load the first batch of comments immediately (using useEffect
) and then load additional pages when a "load more" button is pressed.
The problem is that my current setup causes an infinite loop (caused by the dependency on comments
).
If I remove the fetchNextCommentsPage
function from the useEffect
dependency list, everything seems to work, but EsLint complains about the missing dependency.
const [comments, setComments] = useState<CommentModel[]>([]);
const [commentsLoading, setCommentsLoading] = useState(true);
const [commentsLoadingError, setCommentsLoadingError] = useState(false);
const [paginationEnd, setPaginationEnd] = useState(false);
const fetchNextCommentsPage = useCallback(async function () {
try {
setCommentsLoading(true);
setCommentsLoadingError(false);
const continueAfterId = comments[comments.length - 1]?._id;
const response = await BlogApi.getCommentsForBlogPost(blogPostId, continueAfterId);
setComments([...comments, ...response.comments]);
setPaginationEnd(response.paginationEnd);
} catch (error) {
console.error(error);
setCommentsLoadingError(true);
} finally {
setCommentsLoading(false);
}
}, [blogPostId, comments])
useEffect(() => {
fetchNextCommentsPage();
}, [fetchNextCommentsPage]);
Upvotes: 1
Views: 370
Reputation: 6961
Thank you to @Đào-minh-hạt for their answer. I improved it by passing the continueAfterId
as an argument, rather than holding it in another state (which, I think, is more intuitive):
const [comments, setComments] = useState<CommentModel[]>([]);
const [commentsLoading, setCommentsLoading] = useState(true);
const [commentsLoadingError, setCommentsLoadingError] = useState(false);
const [paginationEnd, setPaginationEnd] = useState(false);
const fetchNextCommentsPage = useCallback(async function (continueAfterId?: string) {
try {
setCommentsLoading(true);
setCommentsLoadingError(false);
const response = await BlogApi.getCommentsForBlogPost(blogPostId, continueAfterId);
setComments(existingComments => [...existingComments, ...response.comments]);
setPaginationEnd(response.paginationEnd);
} catch (error) {
console.error(error);
setCommentsLoadingError(true);
} finally {
setCommentsLoading(false);
}
}, [blogPostId]);
useEffect(() => {
fetchNextCommentsPage();
}, [fetchNextCommentsPage]);
And then in my load-more button's onClick:
<Button
variant="outline-primary"
onClick={() => fetchNextCommentsPage(comments[comments.length - 1]?._id)}>
Load more comments
</Button>
Upvotes: 0
Reputation: 2930
Never put the state you want to mutate in the dependencies list as it will always raise an infinite loop issue.
The common way to solve this is to use the callback function of setState
https://reactjs.org/docs/react-component.html#setstate.
If you want your effect triggered only once, put something that never changes after the first loading. When you want to load more when pressing a button, just change the dependencies of your effect to run your effect again with new dependency value.
const [comments, setComments] = useState<CommentModel[]>([]);
const [commentsLoading, setCommentsLoading] = useState(true);
const [commentsLoadingError, setCommentsLoadingError] = useState(false);
const [continueAfterId, setContinueAfterId] = useState(null)
const [paginationEnd, setPaginationEnd] = useState(false);
const fetchNextCommentsPage = useCallback(async function () {
try {
setCommentsLoading(true);
setCommentsLoadingError(false);
const response = await BlogApi.getCommentsForBlogPost(blogPostId, continueAfterId);
setComments(previousState => [...previousState, ...response.comments]);
setPaginationEnd(response.paginationEnd);
} catch (error) {
console.error(error);
setCommentsLoadingError(true);
} finally {
setCommentsLoading(false);
}
}, [blogPostId, continueAfterId]);
useEffect(() => {
fetchNextCommentsPage();
}, [fetchNextCommentsPage]);
const onButtonPressed = useCallback(() => {
// continueAfterId is one of the dependencies of fetchNextCommentsPage so it will change `fetchNextCommentsPage`, hence trigger the effect
setContinueAfterId(comments[comments.length - 1]?._id)
}, [comments])
Upvotes: 2