DEV Community

Cover image for Quick React GraphQL Tips
Chris Schneider
Chris Schneider

Posted on Edited on

Quick React GraphQL Tips

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)