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
. 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.
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
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):
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:
This is better! ✨
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!
Top comments (3)
That's great;
I don't think this is a good idea in this case, you can clearly see that every time user types any letter query fires immediately, it's not good to try to fetch data on any key press.
This is a contrived example but the query only fetches when the component is rendered, which in this case is when the input value matches
pizza
.