Jordan Nelson

Mocking React/Apollo Data Tables for UI Development

When building a new screen with a data table using React and Apollo, the traditional approach often involves setting up the database, access layers, and a GraphQL query schema before working on the front end. This can be tedious and may require multiple adjustments to the schema to meet the UI's needs.

Instead, consider starting from the front-end UI mockup and working downwards. By developing the front end first, you can use a sample set of data with your data table component, allowing you to focus on the UI without waiting for the back-end setup.

Our data tables support pagination, which is managed through a simple interface:

interface TableProps {
  tableDimensions: {
    rowsPerPage: number;
    pageNumber: number;
  };
}

The table rows builder function injects rows and passes them down to the components. This function acts as a stand-in for the props function in an Apollo query higher-order component. Once the back end is ready, you can replace this with an Apollo query.

export function BuildTableRows(args: { propName: string, rows: T[], isPaginated: boolean }) {
  return (WrappedComponent) => (props: TableProps) => {
    // support simple pagination
    const queryLimit = props.tableDimensions.rowsPerPage;
    const queryOffset = props.tableDimensions.rowsPerPage ? props.tableDimensions.rowsPerPage * props.tableDimensions.pageNumber : 0;

    const propData = args.isPaginated
      ? args.rows.slice(queryOffset, queryOffset + queryLimit)
      : args.rows;

    // propName is the name you'd normally use in the Apollo query props() function.
    const injectedProps = {
      [args.propName]: propData,
      totalRows: args.rows.length,
      loading: false,
    };

    return wrap(WrappedComponent, { ...injectedProps, ...props });
  };
}

Use the table rows builder function to create a higher-order component with dummy data for your UI component.

interface DeliveryTableRow {
  scheduledDate: Date;
  origin: string;
  destination: string;
  carrier: string;
}

const WithDeliveryTableRows = BuildTableRows({
  propName: 'routePlans',
  rows: [
    {
      origin: 'Grand Rapids, MI',
      destination: 'Ann Arbor, MI',
      carrier: 'FedEx',
      scheduledDate: new Date('2018-01-01'),
    },
    {
      origin: 'Allendale, MI',
      destination: 'Ada, MI',
      carrier: 'UPS',
      scheduledDate: new Date('2018-09-01'),
    },
    {
      origin: 'Kalamazoo, MI',
      destination: 'Los Angeles, CA',
      carrier: 'USPS',
      scheduledDate: new Date('2018-10-01'),
    },
  ],
  isPaginated: true,
});

Finally, integrate the higher-order component using Lodash's flowRight to stack components.

export const DataTableWithDeliveryTableRows = flowRight(
  WithDeliveryTableRows,
  // include other data and other wrappers
)(DataTable);

This approach helps quickly identify the minimum data needed for the front end, which can then guide the back-end development, including the GraphQL and database schemas. This reduces refactoring and avoids unnecessary database fields.

Updated and derived from the original version of the article authored by me while working at Atomic Object.