0

The standard way to make an API call in functional React is with useEffect:

function Pizzeria() {
  const [pizzas, setPizzas] = useState([])
  
  useEffect(
    () => fetchPizzas().then(setPizzas),
    []
  )

  return (
    <div>
      {pizzas.map((p, i) => <Pizza pizza={p} key={i} />)}
    </div>
  )
}

But, as this article points out, useEffect will not fire until after the component has rendered (the first time). Obviously in this trivial case it makes no difference, but in general, it would be better to kick off my async network call as soon as possible.

In a class component, I could theoretically use componentWillMount for this. In functional React, it seems like a useRef-based solution could work. (Allegedly, tanstack's useQuery hook, and probably other libraries, also do this.)

But componentWillMount is deprecated. Is there a reason why I should not do this? If not, what is the best way in functional React to achieve the effect of starting an async call early as possible (which subsequently sets state on the mounted component)? What are the pitfalls?

Sasgorilla
  • 2,403
  • 2
  • 29
  • 56
  • You could wrap this component in a different component that makes the call and passes the result to this one. I'm not sure how you could get it to fire any earlier unless you did it further up the tree and stored the result, like maybe in a context provider... – Matt Morgan Oct 22 '22 at 18:55
  • 1
    Also, running after the render _might_ not be a big deal- renders are usually fast, not to be confused with DOM manipulation, which is usually the slowest part of the workflow. – Matt Morgan Oct 22 '22 at 18:57

2 Answers2

2

You're splitting milliseconds here, componentWillMount/render/useEffect all happen at essentially the same time, and the time spent fetching occurs after that. The difference in time from before to after rendering is tiny compared to the time waiting for the network when the request is sent. If you can do the fetch before the component renders, react-query's usePrefetch is nice for that.

Ben West
  • 4,398
  • 1
  • 16
  • 16
1

Considering the scope of a single component, the earliest possible would be to just make the call in the component's function. The issue here is just that such statement would be executed during every render.

To avoid those new executions, you must keep some kind of "state" (or variable, if you will). You'll need that to mark that the call has been made and shouldn't be made again.

To keep such "state" you can use a useState or, yes, a useRef:

function Pizzeria() {
  const pizzasFetchedRef = useRef(false)
  const [pizzas, setPizzas] = useState([])
  
  if (!pizzasFetchedRef.current) {
    fetchPizzas().then(setPizzas);
    pizzasFetchedRef.current = true;
  }

Refs are preferred over state for this since you are not rendering the value of pizzasFetched.

The long story...

Yet, even if you use a ref (or state) as above, you'll probably want to use an effect anyway, just to avoid leaks during the unmounting of the component. Something like this:

function Pizzeria() {
  const pizzasFetchStatusRef = useRef('pending'); // pending | requested | unmounted
  const [pizzas, setPizzas] = useState([])
  
  if (pizzasFetchStatusRef.current === 'pending') {
    pizzasFetchStatusRef.current = 'requested';
    fetchPizzas().then((data) => {
      if (pizzasFetchStatusRef.current !== 'unmounted') {
        setPizzas(data);
      }
    });
  }
  useEffect(() => {
    return () => {
      pizzasFetchStatusRef.current = 'unmounted';
    };
  }, []);

That's a lot of obvious boilerplate. If you do use such pattern, then creating a custom hook with it is the better way. But, yeah, this is natural in the current state of React hooks. See the new docs on fetching data for more info.

One final note: we don't see this issue you pose around much because that's nearly a micro-optimization. In reality, in scenarios where this kind of squeezing is needed, other techniques are used, such as SSR. And in SSR the initial list of pizzas will be sent as prop to the component anyway (and then an effect -- or other query library -- will be used to hydrate post-mount), so there will be no such hurry for that first call.

acdcjunior
  • 132,397
  • 37
  • 331
  • 304