All posts
Blog

Reading Google Sheets data in React with GKit SheetsAPI

Fetch Google Sheets rows in a React component with GKit SheetsAPI. A useEffect example with loading and error states, the real REST endpoint, JSON shape, and caching tips.

3 min read

Why fetch a Google Sheet from React?

A spreadsheet is a surprisingly good content store. Non-technical teammates already know how to edit it, and a single tab can power a pricing table, a changelog, a list of team members, or a feature-flag panel in your app.

The hard part has always been getting that data into the browser. GKit SheetsAPI closes that gap: it turns any Google Sheet into a REST API, so your React component can fetch rows as plain JSON. CORS is enabled, so the call works straight from the browser with no proxy server in between.

SheetsAPI is currently in beta and free while it's in testing.

The endpoint and JSON shape

Listing rows from a tab uses a single GET request:

GET https://sheetsapi.gkit.mreshank.com/api/spreadsheets/{userKey}/{sheetName}

The first row of each tab defines the JSON field names. So a sheet whose header row is name, role, email returns:

[
  { "name": "Ada Lovelace", "role": "Engineer", "email": "ada@example.com" },
  { "name": "Alan Turing", "role": "Researcher", "email": "alan@example.com" }
]

You can shape the response with query params on the list endpoint: limit (max 1000), offset, search=field:value (case-insensitive substring), search_exact, sort (prefix with - for descending), fields (comma list), and format (json, csv, tsv, xml, or jsonp). For example, to grab the 10 most recent engineers, sorted by name, returning only two columns:

GET https://sheetsapi.gkit.mreshank.com/api/spreadsheets/{userKey}/Team?search=role:Engineer&sort=name&limit=10&fields=name,email

A React component that fetches rows

Here's a minimal component that loads rows in useEffect and renders them, with explicit loading and error states. It uses AbortController so a request is cancelled if the component unmounts before it resolves.

import { useEffect, useState } from "react";
 
const BASE = "https://sheetsapi.gkit.mreshank.com/api";
 
function Team({ userKey, sheetName = "Team" }) {
  const [rows, setRows] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
 
  useEffect(() => {
    const controller = new AbortController();
 
    async function load() {
      setLoading(true);
      setError(null);
      try {
        const url = `${BASE}/spreadsheets/${userKey}/${sheetName}?limit=100`;
        const res = await fetch(url, { signal: controller.signal });
        if (!res.ok) throw new Error(`Request failed: ${res.status}`);
        const data = await res.json();
        setRows(data);
      } catch (err) {
        if (err.name !== "AbortError") setError(err.message);
      } finally {
        setLoading(false);
      }
    }
 
    load();
    return () => controller.abort();
  }, [userKey, sheetName]);
 
  if (loading) return <p>Loading…</p>;
  if (error) return <p role="alert">Could not load data: {error}</p>;
 
  return (
    <ul>
      {rows.map((row, i) => (
        <li key={i}>
          <strong>{row.name}</strong> — {row.role}
        </li>
      ))}
    </ul>
  );
}
 
export default Team;

Because the field names come from your header row, row.name and row.role map directly to the columns in your sheet. Rename a column and the JSON key changes with it.

Sending an API key when you need one

A sheet is public until you create an API key. Once you generate one, add an Authorization header to the same fetch call:

const res = await fetch(url, {
  signal: controller.signal,
  headers: { Authorization: "Bearer sk_..." },
});

A word of caution: anything in client-side React is visible to the browser, so don't ship a secret key in a public web app. For read-only public data, leave the sheet public; for anything sensitive, call SheetsAPI from a server route and keep the key there. See the REST API docs for how keys and Google OAuth work, and sign in with Google to create one.

Caching to avoid refetching

Every render of a list shouldn't hit the network. A few practical options:

  • Lift state up. Fetch once in a parent and pass rows down as props, instead of fetching inside each child.
  • Cache in module scope or sessionStorage. Store the JSON keyed by userKey + sheetName and reuse it across mounts within a session.
  • Use a data library. Tools like React Query or SWR give you caching, deduplication, and background revalidation for free — point their fetcher at the SheetsAPI URL above.

Because the list endpoint is a plain GET, it also plays nicely with the browser's HTTP cache and any CDN you put in front of your app.

Next steps

You now have a working read path: a header row defines your JSON, a single GET returns rows, and a useEffect renders them. From here you can add writes — POST to append rows (it accepts one object or an array), PUT to update a row, and DELETE to remove one. The full parameter and method reference lives in the REST API docs, and the product page has the wider picture.

SheetsAPI is in beta and free while it's being tested — a good moment to wire a Sheet into your next React project.

Share