These are some patterns I've found helpful for using GraphQL in a pragmatic fashion to reduce headaches when building applications. These examples will be overly simple, and lean toward being more expressive.
One query !== better
Prop drilling and squeezing all your data into a single query can lead to poor abstraction and unnecessary component coupling. Think about only what is necessary to make a particular component render in isolation.
Before:
function Comments({ value = [] }) {
return value.map(({ body, author }) => (
<p>
{author.name}: {body}
</p>
));
}
function Article() {
// Here I'm querying for both the article AND it's comments.
// This means they will load in and render at the same time.
const { data, loading } = useQuery(ARTICLE);
const { title, body, comments, author } = data;
return loading ? 'Loading article and comments...' : (
<div>
<h1>
"{title}" by: {author.name}
</h1>
<p>{body}</p>
<h4>Comments:</h4>
<Comments value={comments} />
</div>
);
}
After:
function Comments({ value }) {
const { data, loading } = useQuery(COMMENTS, { variables: { id: value }});
return loading
? 'Loading comments...'
: data.comments.map(({ body, author }) => (
<p>
{author.name}: {body}
</p>
));
}
function Article() {
// Independent loading of article data and comment data.
// Allows the separate queries to load/render at their own pace.
const { data, loading } = useQuery(ARTICLE, { variables: { id: 123 } });
const { id, title, body, author } = data;
return (
<div>
{loading ? 'Loading article...' : (
<Fragment>
<h1>
"{title}" by: {author.name}
</h1>
<p>{body}</p>
</Fragment>
)}
<h4>Comments:</h4>
<Comments value={id} />
</div>
);
}
Bind mutations directly to "action" components
Co-locating your mutation methods with any buttons/elements that trigger them enables good control inversion. Parent components will pass contextual information and child components handle execution.
Before:
function Comments({ value, onDelete }) {
const { data } = useQuery(COMMENTS, { variables: { id: value }});
return data.comments.map(({ id, body, author }) => (
<div>
<p>
{author.name}: {body}
</p>
<button onClick={() => onDelete(id)}>Delete</button>
</div>
));
}
function Article() {
const { data } = useQuery(ARTICLE, { variables: { id: 123 } });
const { id, title, body, author } = data;
const [deleteComment] = useMutation(DELETE);
return (
<div>
<h1>
"{title}" by: {author.name}
</h1>
<p>{body}</p>
<h4>Comments:</h4>
<Comments
value={id}
onDelete={id => {
deleteComment({ variables: { id }})
}}
/>
</div>
);
}
After:
function DeleteCommentButton({ value }) {
const [deleteComment] = useMutation(DELETE);
return (
<button onClick={() => deleteComment({ variables: { id: value } })}>Delete</button>
)
}
function Comments({ value }) {
const { data } = useQuery(COMMENTS, { variables: { id: value } });
return data.comments.map(({ id, body, author }) => (
<div>
<p>
{author.name}: {body}
</p>
<DeleteCommentButton value={id} />
</div>
));
}
function Article() {
const { data } = useQuery(ARTICLE, { variables: { id: 123 } });
const { id, title, body, author } = data;
return (
<div>
<h1>
"{title}" by: {author.name}
</h1>
<p>{body}</p>
<h4>Comments:</h4>
<Comments value={id} />
</div>
);
}
Top comments (0)