4.2 Reading Tasks

It's time to query our API for all pending and completed tasks and display them as a list.

Step 1: Task List Item

The first step is to create a list item that will render a single task.

Let's create a new component called TaskListItem . This component will have 2 states:

  1. Loading State: When data is loading from the database. The modern way is to show a placeholder component. To achieve this, we use the @bluebase/plugin-rn-placeholder plugin. This will add the components from the rn-placeholder library in BlueBase. Make sure you install and add this plugin to your project.

  2. Data State: This will render the task data after it is loaded from the database. For this, we will use List.Item component.

See the following code for a reference implementation:

src/components/TaskListItem/TaskListItem.tsx
import { Checkbox, List } from '@bluebase/components';
import { getComponent, useNavigation } from '@bluebase/core';
import { PlaceholderListItemProps } from '@bluebase/plugin-rn-placeholder';
import React, { useCallback } from 'react';

import { Tasks } from '../../graphql-types';

const PlaceholderListItem = getComponent<PlaceholderListItemProps>('PlaceholderListItem');

export interface TaskListItemProps extends Tasks {
	loading?: boolean;
}

export const TaskListItem = (props: TaskListItemProps) => {
	const {
		id,
		title,
		description,
		completed,
		loading,
	} = props;

	if (loading === true) {
		return <PlaceholderListItem avatar description variant="icon" />;
	}

	const { push } = useNavigation();

	const onPress = useCallback(() => {
		push('EditTask', { taskId: id });
	}, [id]);

	return (
		<List.Item
			title={title}
			description={description}
			left={<Checkbox checked={!!completed} disabled />}
			onPress={onPress}
		/>
	);
};

TaskListItem.defaultProps = {};
TaskListItem.displayName = 'TaskListItem';
src/components/TaskListItem/index.ts
export * from './TaskListItem';

import { TaskListItem } from './TaskListItem';
export default TaskListItem;

Step 2: Task List Empty State

We also need to create a component that will be rendered when a list is empty. This component will show an empty message, as well as a call to action button to create a new task:

src/components/TaskListEmptyState/TaskListEmptyState.tsx
import { ComponentState, ComponentStateProps } from '@bluebase/components';
import { useNavigation } from '@bluebase/core';
import React, { useCallback } from 'react';

export interface TaskListEmptyStateProps extends ComponentStateProps {}

export const TaskListEmptyState = (props: TaskListEmptyStateProps) => {
	const { navigate } = useNavigation();

	const goToCreate = useCallback(() => navigate('CreateTask'), []);

	return (
		<ComponentState
			title="No tasks"
			description="Start by creating a new task"
			actionTitle="Create Task"
			imageProps={{ resizeMode: 'contain' }}
			actionOnPress={goToCreate}
			actionProps={{ size: 'small', color: 'success', variant: 'outlined' }}
			{...props}
		/>
	);
};

TaskListEmptyState.displayName = 'TaskListEmptyState';
src/components/TaskListEmptyState/index.ts
export * from './TaskListEmptyState';

import { TaskListEmptyState } from './TaskListEmptyState';
export default TaskListEmptyState;

Step 3: GraphQL Query

Now with these views out of the way, it's time to start creating the actual list. The first step is to write our GraphQL query.

We intend to create our list with infinite scrolling enabled. So, whenever new data is loaded, we need to merge it with the existing data, to show it as one list. To achieve this, we also create a function in this file called TasksCollectionQueryUpdateQueryFn .

src/components/TaskList/TasksCollectionQuery.graphql.ts
import { FetchMoreOptions } from '@apollo/client';
import gql from 'graphql-tag';

import { TasksCollectionQueryQuery } from '../../graphql-types';

export const TasksCollectionQuery = gql`
	query TasksCollectionQuery(
		$filter: tasksFilter
		$first: Int
		# $last: Int
		# $before: Cursor
		$after: Cursor
	) {
		tasksCollection(
			filter: $filter
			first: $first
			# last: $last
			# before: $before
			after: $after
		) {
			edges {
	      cursor
				node {
					id
					title
					completed
				}
			}
			pageInfo {
				endCursor
				hasNextPage
				hasPreviousPage
				startCursor
			}
		}
	}
`;

export const TasksCollectionQueryUpdateQueryFn: FetchMoreOptions<TasksCollectionQueryQuery>['updateQuery'] = (
	previousResult,
	{ fetchMoreResult }
) => {
	if (!fetchMoreResult) {
		return previousResult;
	}

	const prevEdges = previousResult.tasksCollection?.edges || [];
	const newEdges = fetchMoreResult.tasksCollection?.edges || [];

	return {
		// Put the new items at the end of the list and update `pageInfo`
		// so we have the new `endCursor` and `hasNextPage` values
		tasksCollection: {
			...previousResult.tasksCollection,

			edges: [...prevEdges, ...newEdges],

			pageInfo: {
				...previousResult.tasksCollection?.pageInfo,

				endCursor: fetchMoreResult.tasksCollection?.pageInfo?.endCursor,
				hasNextPage: !!fetchMoreResult.tasksCollection?.pageInfo?.hasNextPage,
				hasPreviousPage: !!fetchMoreResult.tasksCollection?.pageInfo?.hasPreviousPage,
				startCursor: fetchMoreResult.tasksCollection?.pageInfo?.startCursor,
			},
		},
	};
};

There is a bug in Supabase that doesn't let us send "last" and "before" params. When it is fixed, these lines should be uncommented.

Step 4: Generate Typings

We will now generate typescript interfaces for our GraphQL API. Execute the following command:

yarn graphql-codegen

This will update the file src/graphql-types.ts.

Step 5: Task List

With all the prep work now complete, let's finally create our list. For this we will use the GraphqlList component from the @bluebase/plugin-json-graphql-components plugin. This component takes care of all the heavy lifting.

We create this component with a complete prop so that we can filter task based on this value. This will be passed as a variable to the GraphQL query.

src/components/TaskList/TaskList.tsx
import { QueryResult } from '@apollo/client';
import { Divider } from '@bluebase/components';
import { getComponent } from '@bluebase/core';
import { GraphqlConnection, GraphqlListProps } from '@bluebase/plugin-json-graphql-components';
import React from 'react';
import { ListRenderItemInfo } from 'react-native';

import { Tasks, TasksCollectionQueryQuery } from '../../graphql-types';
import TaskListEmptyState from '../TaskListEmptyState';
import { TaskListItem, TaskListItemProps } from '../TaskListItem';
import { TasksCollectionQuery, TasksCollectionQueryUpdateQueryFn } from './TasksCollectionQuery.graphql';

const GraphqlList = getComponent<GraphqlListProps<TaskListItemProps, TasksCollectionQueryQuery>>('GraphqlList');

function mapQueryResultToConnection(result: QueryResult<TasksCollectionQueryQuery>) {
	return result.data?.tasksCollection as GraphqlConnection<Tasks>;
}

function renderItem({ item }: ListRenderItemInfo<TaskListItemProps>) {
	return <TaskListItem {...item} />;
}

const renderDivider = () => <Divider inset />;

export interface TaskListProps {
	completed: boolean;
}

export const TaskList = (props: TaskListProps) => {
	const { completed } = props;
	const itemsPerPage = 10;

	return (
		<GraphqlList
			key="task-list"
			pagination="infinite"
			itemsPerPage={itemsPerPage}
			query={TasksCollectionQuery}
			updateQueryInfinitePagination={TasksCollectionQueryUpdateQueryFn}
			mapQueryResultToConnection={mapQueryResultToConnection}
			renderItem={renderItem}
			ItemSeparatorComponent={renderDivider}
			ListEmptyComponent={TaskListEmptyState}
			queryOptions={{
				variables: {
					filter:{ completed: { eq: completed } }
				}
			}}
		/>
	);
};

TaskList.displayName = 'TaskList';

Create the index file:

src/components/TaskList/index.ts
export * from './TaskList';

import { TaskList } from './TaskList';
export default TaskList;

Step 6: Update screens

Last but not the least, lets add this list to the PendingTasksScreen and CompletedTasksScreen screens:

src/screens/PendingTasksScreen/PendingTasksScreen.tsx
import React from 'react';

import { TaskList } from '../../components/TaskList';

export const PendingTasksScreen = () => {
	return (
		<TaskList completed={false} />
	);
};

PendingTasksScreen.displayName = 'PendingTasksScreen';
src/screens/CompletedTasksScreen/CompletedTasksScreen.tsx
import React from 'react';

import TaskList from '../../components/TaskList';

export const CompletedTasksScreen = () => {
	return (
		<TaskList completed />
	);
};

CompletedTasksScreen.displayName = 'CompletedTasksScreen';

Refresh the app, and see it come to life. Enjoy!

Loading State

Empty State

Data State

Last updated