You probably don't need useLazyQuery 🙅‍♂️
Tags: react, graphql, performance, javascript
Published: 2021-03-23 15:36:13

My team maintains a pretty simple (React/Apollo) page that displays a list of items filtered and sorted by various values with corresponding inputs. To reduce database load and eliminate an unused use case, we decided to only query for the list when at least one filter has a value. This meant moving away from fetching via Apollo's `useQuery` hook on every render.

At first the obvious solution appeared to be to swap out the `useQuery` hook for [`useLazyQuery`](https://www.apollographql.com/docs/react/api/react/hooks/#uselazyquery). But since the page has no explicit "Search" button (changing a filter automatically re-queries with updated params), an `effect` would be needed to trigger the now-lazy query.

<img src="https://media.giphy.com/media/l46CyJmS9KUbokzsI/source.gif" />

After setting this up, it didn't feel right and I realized it was because this went against the patterns that hooks were designed to encourage. So to append to the clickbait-y title, you probably don't need it **if your query isn't triggered by an explicit user interaction or event**.

---

Apollo's [`useLazyQuery`](https://www.apollographql.com/docs/react/api/react/hooks/#uselazyquery) provides a function to trigger the query on-demand. This is the purpose of laziness; don't do something now, but maybe do it _later_. But React already has a more elegant way of controlling this behavior: conditional rendering.

Take this component example, using `useLazyQuery` (the initial approach I mentioned):

```jsx
import React, { useState, Fragment } from 'react';
import { useLazyQuery } from 'react-apollo';

const Menu = () => {
    const [food, setFood] = useState('pizza');
    const [search, { data, error, loading }] = useLazyQuery(
        GET_INGREDIENTS,
        { variables: { food } }
    );

    useEffect(() => {
        const shouldSearch = food !== 'pizza';

        if (shouldSearch) {
            search();
        }
    }, [food]);

    return (
        <Fragment>
            <input type='text' onChange={setFood} />
            <Ingredients data={data || []} />
        </Fragment>
    );
};

const Ingredients = ({ data }) => data.map(({ name, description }) => (
    <div key={name}>
        <span>{description}</span>
    </div>
));
```

This code works, although it obscures logic in a `useEffect` that could be hard to find later on. As a component naturally grows in complexity over time, it's important to keep logic like this organized and written as concise as possible so that side-effects don't become a black box.

A simpler approach would be to refactor the `useQuery` logic into into another component and have it only exist when we want it to:

```jsx
const Menu = () => {
    const [food, setFood] = useState('pizza');

    return (
        <Fragment>
            <input type='text' onChange={setFood} />
            <If condition={food !== 'pizza'}>
                <Ingredients food={food} />
            </If>
        </Fragment>
    );
};

const Ingredients = ({ food }) => {
    const { data, error, loading } = useQuery(
        GET_INGREDIENTS,
        { variables: { food } }
    );


    if (error || loading) {
        return null;
    }

    return data.map(({ name, description }) => (
        <div key={name}>
            <span>{description}</span>
        </div>
    ));
};
```
<figcaption>The If syntax comes from the jsx-control-statements package, they're pretty nifty for readability in situations like this.</figcaption>

This is better! ✨

<img src="https://media.giphy.com/media/ALWsTtMzfjai4/source.gif" />

Now, when looking for why a query only occurs in certain states I can see the clear, declarative logic in the `render` instead of needing to dig through the rest of the component.

Again, if the query you need to become lazier is triggered by a user interaction, chances are `useLazyQuery` is the way to go. But if you're sticking to the hook mentality of letting everything be driven by `state`, organizing your components like this could help keep your code easy to follow!