---
title: Code components - Data fetching
---

import { Aside } from '@astrojs/starlight/components';

## SWR

[SWR](https://swr.vercel.app/) is provided as a native package and can be
imported and used for general data fetching. Data returned will be shown in the
"Data Fetch" pane under the component preview.

```jsx
import useSWR from 'swr';

export default function Profile() {
  const { data, error, isLoading } = useSWR(
    'https://my-site.com/api/user',
    fetcher,
  );

  if (error) return <div>failed to load</div>;
  if (isLoading) return <div>loading...</div>;
  return <div>hello {data.name}!</div>;
}
```

![Drupal's Drupal Canvas data fetch pane showing the results of fetching
menus data as JSON](../../../assets/images/data-fetch-1.webp)

## Drupal data

### Page and site data

You can access information about the current page with the `getPageData`
utility. Results can be viewed in the Data Fetch pane below the component
preview.

```js
import { getPageData } from 'drupal-canvas';

const { pageTitle, breadcrumbs, mainEntity } = getPageData();
```

#### Main entity metadata
The main entity is the primary Drupal entity (e.g. article, canvas_page, blog) associated with the current page.
Access main entity metadata of the page you are on with `getPageData`.
This can be used to construct JSON:API parameters for requests.

ℹ️ Not every route has a main entity, in that case `mainEntity` will be null
(e.g. front page, /user/login, or inside the Canvas code editor).

If the code component is added to a region that may appear on all pages (including pages without a main entity),
ensure to check for the existence of `mainEntity` before trying to access its metadata to avoid JS errors. The example below
uses a wrapper component to check for the existence of `mainEntity` and also checks that `mainEntity` is an article node
before calling the `useSWR` hook that fetches related articles. If the code component will only be used
on pages with a main entity, then this check is not necessary.

**Example usage that fetches a list of articles but excludes the current article being viewed:**
```js
import { getPageData } from 'drupal-canvas';
import { JsonApiClient } from 'drupal-canvas';
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
import useSWR from 'swr'

const client = new JsonApiClient();

function RelatedArticles({ mainEntity }) {
  const { bundle, entityTypeId, uuid } = mainEntity;
  const { data, error, isLoading } = useSWR(
  [
    'node--article',
    {
      queryString: new DrupalJsonApiParams()
        .addFilter('id', uuid, '<>') // Exclude current article by uuid.
        .getQueryString(),
    },
  ],
  ([type, options]) => client.getCollection(type, options),
  );

   return (...);
}

// Wrapper component to check for mainEntity existence and type before calling a hook since
// hooks cannot be called conditionally (React rules of hooks).
function RelatedArticlesWrapper() {
  const { mainEntity } = getPageData();

  // Return early if there is no mainEntity, or it is not an article node.
   if (
     !mainEntity ||
     mainEntity.entityTypeId !== 'node' ||
     mainEntity.bundle !== 'article'
   ) {
     return null;
  }
  return <RelatedArticles mainEntity={mainEntity} />;
}

export default RelatedArticlesWrapper;
```

You can also access site information with the `getSiteData` utility

```js
import { getSiteData } from 'drupal-canvas';

const { siteName } = getSiteData().branding;
```

![Drupal's Drupal Canvas data fetch pane showing the results of access page
and site data](../../../assets/images/data-fetch-2.webp)

### JSON:API

[drupal-canvas](https://www.npmjs.com/package/drupal-canvas) provides the
[JSON:API client](https://www.npmjs.com/package/@drupal-api-client/json-api-client)
automatically configured with a `baseUrl` as well as
[DefaultSerializer](https://project.pages.drupalcode.org/api_client/jsonapi-tutorial/deserializing-data/#defaultserializer-top-level-meta-and-links) for
[deserialization](https://project.pages.drupalcode.org/api_client/jsonapi-tutorial/deserializing-data/).
[Drupal core's JSON:API module](https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module)
must be enabled to use this client.

The associated
[parameter helper package](https://project.pages.drupalcode.org/api_client/jsonapi-tutorial/parameters/#generating-query-strings-with-drupal-jsonapi-params)
is also included as a native package.

```jsx
import { JsonApiClient } from 'drupal-canvas';
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
import useSWR from 'swr';

const client = new JsonApiClient();

export default function List() {
  const { data, error, isLoading } = useSWR(
    [
      'node--article',
      {
        queryString: new DrupalJsonApiParams()
          .addInclude(['field_tags'])
          .getQueryString(),
      },
    ],
    ([type, options]) => client.getCollection(type, options),
  );

  if (error) return 'An error has occurred.';
  if (isLoading) return 'Loading...';
  return (
    <ul>
      {data.map((article) => (
        <li key={article.id}>{article.title}</li>
      ))}
    </ul>
  );
}
```

You can override the `baseUrl` and any default options:

```js
const client = new JsonApiClient('https://drupal-api-demo.party', {
  serializer: undefined,
  cache: undefined,
});
```

<Aside type="tip">
  For development outside of Drupal Canvas, use
  [`@drupal-canvas/vite-plugin`](https://www.npmjs.com/package/@drupal-canvas/vite-plugin)
  to automatically provide the necessary configuration based on environment
  variables.
</Aside>

<Aside>
  When working with Code Components outside of Drupal Canvas, you may need to
  configure CORS headers on your Drupal site to allow JSON:API requests from
  your development environment.
</Aside>

### Helper utilities

Utility functions for working with JSON:API and core APIs are provided in the
[`drupal-canvas`](https://www.npmjs.com/package/drupal-canvas) package.

### Examples

#### Fetching nodes with JSON:API

The following example fetches nodes using a preconfigured version of the
[JSON:API client from the Drupal API Client](https://project.pages.drupalcode.org/api_client),
and outputs links to each of them using the `getNodePath` utility from the
`drupal-canvas` package, which will return the path alias if exists, or fall
back to the `/node/[nid]` path.

```jsx
import { getNodePath, JsonApiClient } from 'drupal-canvas';
import { DrupalJsonApiParams } from 'drupal-jsonapi-params';
import useSWR from 'swr';

const Articles = () => {
  const client = new JsonApiClient();
  const { data, error, isLoading } = useSWR(
    [
      'node--article',
      {
        queryString: new DrupalJsonApiParams()
          .addSort('created', 'DESC')
          .getQueryString(),
      },
    ],
    ([type, options]) => client.getCollection(type, options),
  );

  if (error) return 'An error has occurred.';
  if (isLoading) return 'Loading...';
  return (
    <ul>
      {data.map((article) => (
        <li key={article.id}>
          <a href={getNodePath(article)}>{article.title}</a>
        </li>
      ))}
    </ul>
  );
};

export default Articles;
```

#### Using the JSON:API Menu Items module

This example builds a navigation menu using the
[JSON:API Menu Items module](https://www.drupal.org/project/jsonapi_menu_items):

```jsx
import { JsonApiClient, sortMenu } from 'drupal-canvas';
import useSWR from 'swr';

const client = new JsonApiClient();

const Navigation = () => {
  const { data, isLoading, error } = useSWR(
    ['menu_items', 'main'],
    ([type, resourceId]) => client.getResource(type, resourceId),
  );
  if (error) return 'An error has occurred.';
  if (isLoading) return 'Loading...';

  const menu = sortMenu(data);

  return (
    <ul>
      {menu.map((item) => (
        <li key={item.id}>{item.title}</li>
      ))}
    </ul>
  );
};

export default Navigation;
```

#### Using Drupal core's linkset endpoint

You can also build a navigation menu using
[Drupal core's linkset endpoint](https://www.drupal.org/docs/develop/decoupled-drupal/decoupled-menus/decoupled-menus-overview).

```jsx
import { sortLinksetMenu } from 'drupal-canvas';
import useSWR from 'swr';

const Navigation = () => {
  const { data, isLoading, error } = useSWR(
    '/system/menu/main/linkset',
    async (url) => {
      const response = await fetch(url);
      return response.json();
    },
  );
  if (error) return 'An error has occurred.';
  if (isLoading) return 'Loading...';

  const menu = sortLinksetMenu(data);

  return (
    <ul>
      {menu.map((item) => (
        <li key={item.id}>{item.title}</li>
      ))}
    </ul>
  );
};

export default Navigation;
```
