Skip to content

Lesson 1.8

Mary Alice Moore edited this page Jan 4, 2023 · 12 revisions

This lesson will teach you the following:

  • Airtable
  • Data Fetching

Instructions

Overview

With Hooks, it became possible for function components to manipulate state, and have access to a few other parts of React that only class components could use until then.

One of those parts of React is the ability to run code after a component has rendered.

A class component has access to component lifecycle methods such as ComponentDidMount, ComponentDidUpdate, ComponentWillUnmount. These methods only allow class components access to the mounting, updating, and unmounting events of a component.

For example, when the user lands on the page, the component is mounted, then as the states updates, the component is also updated, and, finally, the component is unmounted when the user leaves the page.

But what about functional components?

The useEffect hook gives you the ability to run code after a function component has rendered. By default, it runs after render, and after each update. This default behavior has particularly nasty consequences when writing a useEffect that could trigger a render.

For example, your useEffect triggers a render, the component renders, and useEffect runs again - causing an infinite loop.

Optionally, you can provide an array of dependencies (both variables and functions) for useEffect to watch for changes, and only run your code when any of them change.

Anatomy of the useEffect hook

useEffect(() => {
    // Mounting

    return () => {
        // Cleanup function
    }
}, [//Updating])

The first part is the mounting part. That's when the component is initially rendered when the user lands on the page. The return function is the cleanup function, or when the user leaves the page and the component will unmount.

The array is the last part, and it is where you put the states that will update throughout the component's lifecycle.

The useEffect hook is useful when you want to update the UI when state changes. You can also define a state on first load (i.e. componentDidMount), and also clean the state when the component is unmounting (componentWillUnmount).

import React, { useState } from "react"

const Button = () => {
    const [count, setCount] = useState(0)

    return <button onClick={() => setCount(count + 1)}>You clicked {count} times</button>;
}

export default Button

Fetch Data with useEffect

It's very common, when building an application, to fetch data from an API.

When a user lands on our page, we want to call the API. In other words, we want to call the API during the mounting part of the component's lifecycle.

Note: We will use the use-case of connecting and writing to the Airtable API in our examples:

We connect to the API with the proper key and authorization via the Fetch API. Responding to server error(s) are handled by the try/catch but if there is a 4xx client error, checking the** response.ok **property tells us if the response was successful or not.

After fetching the response object, we first need to convert the data stream to JSON with response.json(). We must retain the id field returned by the Airtable API so it can be used for CRUD operations such as writing, updating and deleting.

The data stored in a table is returned in a fields object with each property a field in the table you created earlier. In this example, there is just one property of name stored in fields.

After retrieving the data it can them be used in local state of whichever component needs to store it to be re-rendered to the UI.

Reading (CRUD)

  const loadTodos = async() => {
 
    try {
 
    const response = await
       fetch(`https://api.airtable.com/v0/{API key}`, {
           headers: {
             'Authorization': `Bearer ${API_KEY}`
          }
       });

       if (!response.ok) {
         const message = `Error: ${response.status}`;
         throw new Error(message); 
       }

       const todosFromAPI = await response.json();

       const todos = todosFromAPI.records.map((todo) => {

          const newTodo =  {
              id: todo.id,
              name: todo.fields.name
          }

          return newTodo
            
      });
    
         setTodos(todos);
 
      } catch (error) {
          console.log(error.message)
      }

Creating (CRUD)

With creating using a POST method, the Airtable API requires new record(s) to be wrapped in an object within a fields property. The data sent to the Airtable API will be sent in the body of the response.

  const postTodo = async (todo) => {

    try {

       const airtableData = {
          fields: {
             name: todo
          }
       }

       const response = await 
          fetch(`https://api.airtable.com/v0/{API key}/todos`, 
          {
             method: 'POST',
             headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${API_KEY}`
             },
             body: JSON.stringify(airtableData)
      });

      if (!response.ok) {
        const message = `Error has ocurred:
                         ${response.status}`;
        throw new Error(message);
      }

      const dataResponse = await response.json();
      return dataResponse;

    } catch (error) {

          console.log(error.message);
          return null;
    }
  }

Now put it into action...

Setup Airtable

  1. Sign up (or login) for an Airtable account: https://airtable.com/signup
  2. Create a new base
  3. Choose a Title, Icon, and Color for your base
  4. Rename "Table 1" to "Default"
  5. Within the table, rename the first column to "Title" and delete the other columns
  6. Add one or more todo items to the table

Create Environment File

  1. Open code editor
  2. Create a new file named .env.local in the root directory

dotenv files use the syntax VARIABLE=value (See official rules)

Generate Airtable API Key

  1. Navigate to https://airtable.com/account
  2. Click "Generate API Key"
  3. Copy the key from the input field
  4. Open .env.local
  5. Create a new variable named REACT_APP_AIRTABLE_API_KEY
  6. Paste the key as its value

Connect to Airtable API

  1. Click "Help" button
  2. Click "API Documentation" link
  3. Copy the "Base ID"
  4. Open .env.local
  5. Create a new variable named REACT_APP_AIRTABLE_BASE_ID
  6. Paste the ID as its value

Fetch Data from Airtable

  • Open src/App.js
  • Inside the first useEffect hook, replace the placeholder Promise with the following...
  • Using the Fetch API, create a "GET" request and pass the necessary arguments:
    • url: the url of your request (in this case, https://api.airtable.com/v0/${process.env.REACT_APP_AIRTABLE_BASE_ID}/Default note that we need to use process.env... to get env variables and remember that you need to use back-ticks and a dollar sign to do string formatting (see Using Fetch - Web APIs | MDN (Supplying request options section) for a reminder if needed)
    • options: the object of additional request options
  • In the fetch options object, add the property headers as an object with the following property:
    • Authorization: the token to authorize the request (in this case "Bearer {API_KEY}" where {API_KEY} is replaced with the corresponding environment variable)
  • Chain a then method to your fetch call and pass it a function that returns the response JSON data
  • Update the setToDoList call to reference the new result format (hint: result.records)
  • Open src/TodoListItem.js
  • Update the todo item title to reference the new object format (hint: todo.fields.Title)

Stretch Goal (OPTIONAL)

If you'd like to stretch your skills and attempt creating/writing data to AirTable from your app, give it a try! One thing to note before you attempt: make sure your onAddTodo function has the same data structure as the AirTable data so you don't wind up having data added to the wrong column or get an error as a result of mismatched data.