• Home
  • Contact

How to Build a Task Manager Application Using React, Airtable and GraphQL

Jesus Manuel Olivas
June 01, 2021

After spending most of my days doing Product Management work lately, I decided to spend some time building a PoC application to manage daily tasks for my kids.

The Idea

The goal was to build it using ReactJS and write as little code as possible. Yes, the plan relied on taking advantage of tools & libraries to reduce the needed code for this first iteration.

The Data Storage

Airtable was chosen for the data storage to avoid creating and maintaining a DB schema and/or GraphQL DSL, also to avoid creating a GUI to manage (review/filter/group) the tasks.

The app was simple enough to only require the creation of three tables, Users, Tasks and UserTasks.


Since GraphQL was prefered as endpoint to expose the Airtable data. The decision to use BaseQL was made to take care of this duty without writing any line of code.

While exposing the GraphQL API via BaseQL a few limitations got discovered. The main issue was related to sorting data using nested elements userTasks->task->weight and the implemented work-around was creating a new Lookup field on the UserTask <-> Task linked record to provide a new field available as userTasks->taskWeight not pretty, but it works as expected.

This is how the Lookup field configuration looks like:

A similar approach was used for the UserTasks->userName field used as a grouping field at the Airtable GUI.

Once the previous configuration was added, it was possible to do the sorting using the taskWeight field.

export const USER_TASKS_QUERY = gql` query userTasks($user: String, $date: String) { userTasks( _filter: { user: [$user], date: $date } _order_by: [{ taskWeight: "asc" }] ) { ...userTasks } } `;

Probably spending more time reading the BaseQL docs is required to resolve this without adding the Lookup field.

BaseQL provide a really intersting feature which allows you to expose mutations on your GraphQL API by updating a configuration option via the application interface as show here:

After doing this configuration the following GraphQL mutation can now be executed:

export const UPDATE_USER_TASK = gql` mutation updateUserTasks($userTaskId: String, $completed: Boolean) { update_userTasks(id: $userTaskId, completed: $completed) { ...userTasks } } `;

From the application callback function, this is how the mutation code looks like:

const handleUserTaskChange = (checked, id) => { const mutation = client.mutate({ mutation: UPDATE_USER_TASK, variables: { userTaskId: id, completed: checked, }, }); mutation .then((result) => { // @TODO do something on success }) .catch((error) => { // @TODO do something on error }); };


For the Table/Grid UI interface, the decision to use TableQL created by @danilo_zekovic was made because of the out-of-the-box and easy to use features.

This is the required code to render a table using this library:

<ApolloTableQL pollInterval={15000} columns={columns} query={USER_TASKS_QUERY} variables={{ user: user.username, date: format(date, "yyyy-MM-dd"), }} />

TableQL allows you to control how to render the data by providing an array containing the columns you want to render.

const columns = [ "taskName", { id: "completed", component: (props) => ( <Switch id={props.id} onChange={(e) => handleUserTaskChange(e, props.id)} checked={props.completed === true} disabled={today !== props.date} /> ), customColumn: true, }, ];

In this example, instead of rendering the completed field value, the code is rendering a ReactJS component.

The other UI elements

The Application Auth and User Management.

After a quick research ClerkDev was found, and by spending less that 30 min to get the create-react-app working with the following features (user auth, SSO using Google, multi-user session for testing, multiple environments among others) the desicion was made to use this tool.

You can see the code used for sign-in/sign-up and validate rendering the app once the user was logged in:

function App() { return ( <ClerkProvider frontendApi={frontendApi}> <SignedOut> <SignIn /> </SignedOut> <SignedIn> <ApolloProvider client={client}> <TimeTracker /> </ApolloProvider> </SignedIn> </ClerkProvider> ); }

Minimum required clerk settings for this example to work:

Make sure the username on clerk matches the user->name at Airtable, because that is used to query the data via the GraphQL endpoint. Having a way to sync data between Clerk and Airtable will be ideal and worth some research time.

Adding an Airtable Script to Automate Daily Data Creation

After adding Clerk to take care of the user management, it seems the Users table is no longer needed, but is kept because it was used on a script to populate the UsersTasks records at midnite.

Javascript code used as part of the daily data creation:

const UserTasks = base.getTable("UserTasks"); const Users = base.getTable("Users"); const UsersRecords = await Users.selectRecordsAsync(); const users = UsersRecords.records; const Tasks = base.getTable("Tasks"); const TasksRecords = await Tasks.selectRecordsAsync(); const tasks = TasksRecords.records; const getDate = function () { const date = new Date(); const month = date.getMonth()+1; const day = date.getDate(); return date.getFullYear() +'-'+ (month<10 ? '0' + month : month) +'-'+ (day<10 ? '0'+ day : day); }; const date = getDate(); users.forEach(user => { tasks.forEach(task => { UserTasks.createRecordAsync({ User: [user], Task: [task], Completed: false, Date: date }) }) })

The final result

This is how the app looks like, pretty simple but functional.

Login via Clerk:

Managing Tasks:

The WrapUp

I am fully aware there are other many tools for each one of those needs, FaunaDB + FaunAdmin, Firebase + Firetable, 8Base, Supabase, Sanity, Prismic, ReactTable, MagicLink and many, many others (some of those tools we curently use or had used in the past at Octahedroid).

But I used the mentioned tools because are the best tools for the problem I was trying to resolve and provide an easy path and less friction to implement at this very moment.

If you want to try this you can:

  • Clone this code in your local machine Github Code Repository.
  • Copy this an Airtable base.
  • Add the BaseQL application to your Airtable.
  • Turn on the DailyTaskCreation automation.
  • Create a Clerk account and add a new application applying the settings mentioned on this post.
  • Copy paste the file .env.example as .env and update:
    • The REACT_APP_CLERK_FRONTEND_API value from your Clerk application.
    • The REACT_APP_GRAPHQL_URI value from your BaseQL application.

In order to install Airtable apps as BaseQL and take advantage of running scripts on automations you will need to have a paid plan.

Ready to embrace the JAMstack revolution?

Build fast and secure sites and apps delivered by pre-rendering files and serving them directly from a CDN, removing the requirement to manage or run web servers, databases and worry about traffic spikes.
Work with us!