DEV Community

Cover image for Declarative rendering with Apollo Client results
Mike Schutte
Mike Schutte

Posted on

1 2

Declarative rendering with Apollo Client results

RedwoodJS introduces a great abstraction for dealing with query results from Apollo Client: cells.

If you've used Apollo Client before, you've probably written something like the following hundreds of times.

const { loading, data } = useQuery(...)
if (loading) {
  return ...
}

if (data.length === 0) {
  return ...
}

return (
  ...
)
Enter fullscreen mode Exit fullscreen mode

Am I wrong?

I love the idea of cells. I can tell it's a great abstraction because there's no need to port my whole app over to RedwoodJS to get the same immediate declarative improvements. Here is a Redwoods-y utility function to render the result of a GraphQL query in any codebase with Apollo Client query results.

import * as React from "react";
import { ApolloError, QueryResult } from "@apollo/client";

const isEmpty = (data: NonNullable<QueryResult["data"]>): boolean => {
  const dataValue = data[Object.keys(data)[0]];
  return (
    dataValue === null || (Array.isArray(dataValue) && dataValue.length === 0)
  );
};

export const renderResult = <T extends QueryResult>(
  result: T,
  options: {
    isEmpty?: (data: NonNullable<T["data"]>) => boolean;
    Loading: React.FC;
    Failure: React.FC<{ error: ApolloError }>;
    Empty: React.FC;
    Success: React.FC<{ data: NonNullable<T["data"]> }>;
  }
): React.ReactElement => {
  return result.loading ? (
    <options.Loading />
  ) : result.error ? (
    <options.Failure error={result.error} />
  ) : (options.isEmpty ?? isEmpty)(result.data) ? (
    <options.Empty />
  ) : (
    <options.Success data={result.data} />
  );
};
Enter fullscreen mode Exit fullscreen mode

We can pass a custom isEmpty function if the shape of our data is more unique than the basic case.

Example usage:

import * as React from "react";

import { render } from "lib/render-result";

const MyComponent: React.FC = () => {
  const result = useQuery(...)
  return render(result, {
    Loading,
    Failure,
    Success,
    Empty,
    isEmpty: (data) => data.customPath.toCheck.length === 0
  });
};

export default MyComponent;

const Loading: React.FC = () => {
  return ...
};

const Empty: React.FC = () => {
  return ...
};

type Data = NonNullable<QueryResult["data"]>;
const Success: React.FC<{ data: Data }> = ({ data }) => {
  return ...
};

type FetchError = NonNullable<QueryResult["error"]>;
const Failure: React.FC<{ error: FetchError }> = ({ error }) => {
  console.error(error);
  return ...
};
Enter fullscreen mode Exit fullscreen mode

✌️

Top comments (1)

Collapse
 
ajcwebdev profile image
ajcwebdev

Awesome to see this pattern being picked up. Bison is another framework that has adopted cells.

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more