简体   繁体   English

如何使用 useEffect 和 useCallback 摆脱这个无限循环?

[英]How to get rid of this infinite loop using useEffect and useCallback?

I wanna load the first batch of comments immediately (using useEffect ) and then load additional pages when a "load more" button is pressed.我想立即加载第一批评论(使用useEffect ),然后在按下“加载更多”按钮时加载其他页面。

The problem is that my current setup causes an infinite loop (caused by the dependency on comments ).问题是我当前的设置导致无限循环(由对comments的依赖引起)。

If I remove the fetchNextCommentsPage function from the useEffect dependency list, everything seems to work, but EsLint complains about the missing dependency.如果我从useEffect依赖项列表中删除fetchNextCommentsPage function,一切似乎都正常,但EsLint抱怨缺少依赖项。

    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]);

Never put the state you want to mutate in the dependencies list as it will always raise an infinite loop issue.永远不要把你想改变的 state 放在依赖列表中,因为它总是会引发无限循环问题。 The common way to solve this is to use the callback function of setState https://reactjs.org/docs/react-component.html#setstate .解决这个问题的常用方法是使用setState https://reactjs.org/docs/react-component.html#setstate 的回调 function

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])

Thank you to @Đào-minh-hạt for their answer.感谢@Đào-minh-hạt 的回答。 I improved it by passing the continueAfterId as an argument, rather than holding it in another state (which, I think, is more intuitive):我通过将continueAfterId作为参数传递来改进它,而不是将它保存在另一个 state 中(我认为这更直观):

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:然后在我的加载更多按钮的 onClick 中:

<Button
   variant="outline-primary"
   onClick={() => fetchNextCommentsPage(comments[comments.length - 1]?._id)}>
    Load more comments
</Button>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM