deinnielle
deinnielle

Reputation: 69

React useEffect and return render

I have a useEffect with fetch and I would like to render it first, since it runs the else case first and then runs the useEffect. Which shows the else case then the posts when viewing the page. Is there a way to render useEffect first?

const [posts, setPosts] = useState([]);

useEffect(() => {
  const data = await fetch('https://localhost:1111/api/posts', {
    credentials: 'include'
  });
  const response = await data.json();
  setPosts(response);
}, []);

return (
  <div>
    {posts.length > 0 ? (
      posts.map(post => (
        <Post key={post.id} onUpdate={() => handleUpdate()} post={post} />
      ))
    ) : (
      <p>No posts</p>
    )}
  </div>
);

Upvotes: 3

Views: 13959

Answers (3)

Nicholas Tower
Nicholas Tower

Reputation: 84982

You can get that result, but you'll need to do it a different way.

When your component renders for the first time, the entire function will run, and then the page will be updated based on what your function returned. This happens more or less synchronously. Only after it has rendered will your effects run. It's not possible to change the order of this: render is always first, effects are always after the render.

So if you don't want anything to display until that effect can complete, you need to return a null from the render. Then when the fetch is complete, you update state, which causes it to render again, this time not with a null.

For example, here i've taken your code and added one more possibility: the state might be null. I'm using this to indicate that it hasn't loaded yet. Later, once the data has been fetched, the state gets updated and the component rerenders.

const [posts, setPosts] = useState(null);

useEffect(() => {
  (async () => {
    const data = await fetch('https://localhost:1111/api/posts', {
      credentials: 'include'
    });
    const response = await data.json();
    setPosts(response);
  })();
}, []);

if (posts === null) {
  return null;
}

return (
  <div>
    {posts.length > 0 ? (
      posts.map(post => (
        <Post key={post.id} onUpdate={() => handleUpdate()} post={post} />
      ))
    ) : (
      <p>No posts</p>
    )}
  </div>
);

Upvotes: 4

Hurobaki
Hurobaki

Reputation: 4068

You use useEffect as componentDidMount because you provide an empty array as dependency. As its name suggests it's call after your component first render.

So to answer to your question, no you can't render your fetch result as first render. You can use a Loader component to improve UX and wait until your data get fetched.

Or you can fetch your data in a parent component and pass it down as prop so you can first render with your fetched data.

Upvotes: 0

Jeff Storey
Jeff Storey

Reputation: 57192

You could set posts to null by default, rather than an empty array. Then render nothing (or a spinner) if posts is null. This would prevent flashing "no posts" while the data fetching is occurring.

Upvotes: 0

Related Questions