Search code examples
reactjsnext.jsreact-hooksnext-router

Change in the order of hooks when calling API inside useEffect


I have the following component:

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;

the Post Details component:

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;

When the Post component loads I get this error(If needed I can post the full error): 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)

It does render correctly but I still want to fix the error. It looks like it's pointing at the api call inside the useEffect hook, but I'm not sure what's wrong with it. An interesting thing I found is that if I remove the use of nextjs router in the PostDetails component the error does not appear, but I need it to have a navigation functionality.

I tried to read about the rules of hooks, checked I always initialize them unconditionally, and inside the component. I didn't find any info in the next router docs that are related to the issue.


Solution

  • You are calling your component as a regular function PostDetails(post, comments). Instead you need to call it as a JSX React Component, i.e. <PostDetails post={post} comments={comments} />, otherwise React can't handle hooks correctly.

    Just to give your more info, if we expand your code you are basically calling useRouter after your "loading" if block:

      if (!post) {
        return <p>Loading...</p>;
      }
    
      return (
        <Grid container spacing={2}>
          { // This call is basically inlined in runtime
            // because it's just a regular function
            PostDetails(post, comments)
            
            // It expands in something like this:
            const router = useRouter()
            
            // And then you use your router here
            // But this whole code comes after an `if` block
            // which is against the rules of hooks
          }
        </Grid>
      )