You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

useEffect内调用API时Hook调用顺序变更警告的修复求助

解决React Hook调用顺序警告问题

问题重现

PostPage组件代码

const PostPage = () => {
  const [post, setPost] = useState(null);
  const [comments, setComments] = useState([]);
  const router = useRouter()
  
  useEffect(() => {
    if (router.isReady) {
      apiClient.get(`/posts/${router.query.id}/`)
      .then((response) => {
        setPost(response.data)
        apiClient.get(`/comments/`).then((response) => {
          setComments(response.data)
        })})
      .catch((error) => console.error('Error fetching post:', error));
    };
  }, [router.isReady]);

  if (!post) {
    return <p>Loading...</p>;
  }

  return (
    <Grid container spacing={2}>
      {PostDetails(post, comments)}
    </Grid>
  )
  
};

export default PostPage;

PostDetails组件代码

const PostDetails = (post={}, comments=[]) => {
const { title, author, content} = post;

const router = useRouter()
  return (
    <div>
      {/* <Button variant="outlined" color="primary" onClick={() => router.back()}>Close Post</Button> */}
      <Typography variant="h2">{title}</Typography>
      <Typography variant="subtitle1">Author: {author}</Typography>
      <Typography variant="body1" >{content}</Typography>
      <Typography variant="h3">Comments:</Typography>
      <List>
        {comments.map((comment, index) => (
          <Comment author={comment.author} text={comment.text} index={index}/>
        ))}
      </List>
    </div>
  );
};

export default PostDetails;

警告信息

Warning: React has detected a change in the order of Hooks called by PostPage.

Previous render Next render

  1. useState useState
  2. useState useState
  3. useContext useContext
  4. useEffect useEffect
  5. undefined useContext ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at PostPage (webpack-internal:///./pages/posts/[id].jsx:20:76)

问题原因

核心问题是Hook调用顺序不一致:

  • 第一次渲染时,post为null,PostPage直接返回<p>Loading...</p>,PostDetails组件不会被渲染,因此其中的useRouter(内部依赖React Context)不会执行。
  • 当post加载完成后,第二次渲染PostPage会渲染PostDetails,此时useRouter被调用,相当于在PostPage的渲染流程中新增了一个Hook调用,违反了React Hook的「每次渲染Hook调用顺序必须一致」规则。

解决方案

方案1:将路由逻辑上移到PostPage,通过props传递

把useRouter从PostDetails移到PostPage,将导航函数作为props传给PostDetails,这样PostDetails不再依赖Hook,避免渲染顺序导致的问题。

修改后的PostPage:

const PostPage = () => {
  const [post, setPost] = useState(null);
  const [comments, setComments] = useState([]);
  const router = useRouter()
  
  useEffect(() => {
    if (router.isReady) {
      apiClient.get(`/posts/${router.query.id}/`)
      .then((response) => {
        setPost(response.data)
        apiClient.get(`/comments/`).then((response) => {
          setComments(response.data)
        })})
      .catch((error) => console.error('Error fetching post:', error));
    };
  }, [router.isReady]);

  if (!post) {
    return <p>Loading...</p>;
  }

  return (
    <Grid container spacing={2}>
      <PostDetails post={post} comments={comments} onBack={() => router.back()} />
    </Grid>
  )
  
};

export default PostPage;

修改后的PostDetails:

const PostDetails = ({ post={}, comments=[], onBack }) => {
const { title, author, content} = post;

  return (
    <div>
      <Button variant="outlined" color="primary" onClick={onBack}>Close Post</Button>
      <Typography variant="h2">{title}</Typography>
      <Typography variant="subtitle1">Author: {author}</Typography>
      <Typography variant="body1" >{content}</Typography>
      <Typography variant="h3">Comments:</Typography>
      <List>
        {comments.map((comment, index) => (
          <Comment author={comment.author} text={comment.text} index={index}/>
        ))}
      </List>
    </div>
  );
};

export default PostDetails;

方案2:将Loading逻辑移到PostDetails内部

确保PostDetails在每次渲染时都被调用,避免Hook调用顺序变化。修改PostPage去掉提前返回,把Loading逻辑放到PostDetails里:

修改后的PostPage:

const PostPage = () => {
  const [post, setPost] = useState(null);
  const [comments, setComments] = useState([]);
  const router = useRouter()
  
  useEffect(() => {
    if (router.isReady) {
      apiClient.get(`/posts/${router.query.id}/`)
      .then((response) => {
        setPost(response.data)
        apiClient.get(`/comments/`).then((response) => {
          setComments(response.data)
        })})
      .catch((error) => console.error('Error fetching post:', error));
    };
  }, [router.isReady]);

  return (
    <Grid container spacing={2}>
      <PostDetails post={post} comments={comments} />
    </Grid>
  )
  
};

export default PostPage;

修改后的PostDetails:

const PostDetails = ({ post={}, comments=[] }) => {
  const router = useRouter()

  if (!post) {
    return <p>Loading...</p>;
  }

  const { title, author, content} = post;

  return (
    <div>
      <Button variant="outlined" color="primary" onClick={() => router.back()}>Close Post</Button>
      <Typography variant="h2">{title}</Typography>
      <Typography variant="subtitle1">Author: {author}</Typography>
      <Typography variant="body1" >{content}</Typography>
      <Typography variant="h3">Comments:</Typography>
      <List>
        {comments.map((comment, index) => (
          <Comment author={comment.author} text={comment.text} index={index}/>
        ))}
      </List>
    </div>
  );
};

export default PostDetails;

两种方案都能解决Hook调用顺序不一致的问题,推荐方案1,因为它更符合组件职责分离的原则,把路由逻辑放在页面组件中,子组件只负责展示和接收回调。

内容的提问来源于stack exchange,提问作者kirill ras

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 04:35:59