> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Lab - Add Dynamic Data

Plan: B2B Developer

Lesson 17 of 25 · 20 min

## Introduction

The "Recent Orders" section on our custom Overview page is currently populated with mock data. In this exercise, we'll replace this with real data fetched via the B2B GraphQL API.

### In this lab, you will:

* Write a GraphQL query to fetch recent orders
* Replace mock data with real data
* Add several more data-driven sections to the Overview page

## Setup

This exercise continues where you left off previously. Make sure the dev server is started in your Buyer Portal project:

```shell showLineNumbers={false}
yarn dev
```

If you need a fresh start, you can follow the instructions below to set up a new project complete with previous exercise code.

1. **Copy** the project with the appropriate tag.

```shell showLineNumbers={false}
degit https://github.com/bigcommerce-edu/lab-b2b-buyer-portal.git#e-gql-pre <directory-name>
```

2. **Install** dependencies.

```shell showLineNumbers={false}
cd <directory-name>
yarn install
```

3. **Run** the dev server.

```shell showLineNumbers={false}
yarn dev
```

> **Info**
>
> **Remember!**
>
> Script Manager in your store control panel must contain appropriate header/footer scripts to load the Buyer Portal from your local environment on the appropriate port.
>
> Revisit the initial setup lab for full details on setting up your environment.

### Exercise Reference Copy

If it's helpful to compare your own code as you go, you can clone or download the completed version of this labs as a reference.

[GraphQL Lab Snapshot](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/tree/e-gql-post)

## Step 1: Query for Recent Orders

> **Info**
>
> Remember, all file paths referenced in these exercises are relative to `apps/storefront/src` in your Buyer Portal project.

The `data.ts` file in our page component's directory already contains GraphQL logic for several sections we'll be adding in this exercise. This file doesn't yet contain the proper GraphQL for recent orders.

1. **Open** `pages/Overview/data.ts` and **add** a response interface and GraphQL query string.

**`Overview/data.ts`**

```tsx {1-9, 17-36} showLineNumbers={false} title="Overview/data.ts"
interface RecentOrdersResponse {
  data: {
    allOrders: {
      edges: {
        node: OverviewOrder;
      }[]
    }
  }
}

interface RecentInvoicesResponse {
  ...
}

...

const RecentOrdersQuery = `
  query GetRecentOrders(
    $limit: Int,
    $sort: String
  ) {
    allOrders(
      first: $limit,
      orderBy: $sort
    ){
      edges {
        node {
          orderId
          createdAt
          totalIncTax
          poNumber
        }
      }
    }
  }
`;

const RecentInvoicesQuery = `
...
`;
```

It's not strictly important where in the file this code is added. `RecentOrdersResponse` defines what we expect the response to our GraphQL query to look like. Note that each `node` is expected to be an `OverviewOrder`, which is another interface already defined with fields such as `orderId`, `createdAt`, and `poNumber`.

Note that the interface describes the **full GraphQL envelope**, wrapping `allOrders` in a `data` property. This matches the shape the API actually returns, and it's the convention the Buyer Portal core uses for its own response interfaces. You'll see shortly that the value our code receives is already unwrapped.

> **Info**
>
> The fields of `OverviewOrder` should be familiar, as our "Recent Orders" table is already configured with columns matching these keys. The mock data currently populating the table matches this schema.

`RecentOrdersQuery` is the GraphQL query itself, using the `allOrders` field to fetch a list of records. Note the query is written to support sorting and paging variables, making it flexible for reuse.

2. **Add** the `getRecentOrders` function.

**`Overview/data.ts`**

```tsx {1-11} showLineNumbers={false} title="Overview/data.ts"
export const getRecentOrders = async () => {
  const resp = await B3Request.graphqlB2B<RecentOrdersResponse>({
    query: RecentOrdersQuery,
    variables: {
      limit: 5,
      sort: "-createdAt",
    },
  });

  return resp.allOrders?.edges.map((edge) => edge.node) ?? [];
};

export const getRecentInvoices = async () => {
  ...
};
```

Our code doesn't need to concern itself with how the user token used to authenticate the GraphQL request is managed or retrieved, with `B3Request` handling this for us.

Note the use of `map` to form a flattened array of orders from each node in `allOrders.edges` in the response. `getRecentOrders` will return an array where each item has an `orderId`, `poNumber`, etc.

> **Info**
>
> Typing the response tells TypeScript what to expect at compile time, but doesn't validate at runtime that the response data actually matches. Our code also currently lacks any error handling for the GraphQL fetch. [Zod](https://zod.dev/), which was used in the previous "mini-app" exercises, can be used in the same way within the Buyer Portal as well.

3. **Open** `pages/Overview/components/RecentOrders.tsx`, **update** the import from `../data` to include the new function, and **remove** the mock data array.

**`RecentOrders.tsx`**

```tsx showLineNumbers={false} title="RecentOrders.tsx"
import { getRecentOrders, OverviewOrder } from "../data";

...

/* Remove this
const mockOrders = [
  ...
];
*/
```

4. In the `useEffect` callback, **replace** the current `setOrders` call with a call to `getRecentOrders`.

**`RecentOrders.tsx`**

```tsx {11-13} showLineNumbers={false} title="RecentOrders.tsx"
export default function RecentOrders({
  ...
}: OrdersProps) {
  ...

  const [orders, setOrders] = useState<OverviewOrder[]>([]);

  useEffect(() => {
    if (!startLoad) return;

    getRecentOrders().then((b2bOrders) => {
      setOrders(b2bOrders);
    });
  }, [startLoad]);

  const orderColumns = [
    ...
  ];
}
```

The `orders` state will be updated when the response is received from the GraphQL request.

Now that we're loading real data, let's complete the "spinner" implementation by adding a piece of React state to track when the data is loading.

5. **Add** a state variable and **update** the `useEffect` callback and JSX accordingly.

**`RecentOrders.tsx`**

```tsx {8, 11, 15, 17, 22} showLineNumbers={false} title="RecentOrders.tsx"
export default function RecentOrders({
  ...
}: OrdersProps) {
  ...

  const [orders, setOrders] = useState<OverviewOrder[]>([]);

  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!startLoad || !loading) return;

    getRecentOrders().then((b2bOrders) => {
      setOrders(b2bOrders);
      setLoading(false);
    });
  }, [startLoad, loading]);

  ...

  return (
    <B3Spin isSpinning={loading}>
      ...
    </B3Spin>
  );
}
```

Remember that we've already configured our component to defer triggering this data fetching until the accordion is expanded.

**Revisit your Overview page and see the Recent Orders fetch in action. Try using the Network tab in your browser's developer tools to inspect the GraphQL request and response.**

> **Info**
>
> [**View the Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-gql-01a-pre...e-gql-01b-post?diff=split)

## Step 2: Add Other Data-Driven Sections

As this is an Overview page, we want to show more than just recent orders. There are already components and GraphQL queries in place in our boilerplate code for invoices, shopping lists, and quotes. Let's add these remaining sections.

1. **Open** `pages/Overview/index.tsx` and **add** imports for the other components.

**`Overview/index.tsx`**

```tsx showLineNumbers={false} title="Overview/index.tsx"
import RecentInvoices from "./components/RecentInvoices";
import RecentShoppingLists from "./components/RecentShoppingLists";
import RecentQuotes from "./components/RecentQuotes";
```

2. Similar to the `ordersOpen` state we're using to track when the "Recent Orders" accordion is open, **add** state values for the other sections.

**`Overview/index.tsx`**

```tsx {5-7} showLineNumbers={false} title="Overview/index.tsx"
export default function Overview() {
  ...

  const [ordersOpen, setOrdersOpen] = useState<boolean>(false);
  const [invoicesOpen, setInvoicesOpen] = useState<boolean>(false);
  const [shoppingListsOpen, setShoppingListsOpen] = useState<boolean>(false);
  const [quotesOpen, setQuotesOpen] = useState<boolean>(false);

  const { getOrderPermission } = useAppSelector(rolePermissionSelector);

  ...
}
```

Remember that we've used selective permission checking to control whether "Recent Orders" is displayed. We'll need to add similar logic for the other sections.

3. **Extend** the existing destructuring to pull out permission flags for the new sections.

**`Overview/index.tsx`**

```tsx {4-9} showLineNumbers={false} title="Overview/index.tsx"
export default function Overview() {
  ...

  const {
    getOrderPermission,
    getInvoicesPermission,
    getShoppingListPermission,
    getQuotesPermission,
  } = useAppSelector(rolePermissionSelector);

  return (
    ...
  );
}
```

Because `rolePermissionSelector` returns a flag for every available permission, adding a new check is just a matter of destructuring another property. There's no additional function call or configuration per section.

4. **Add** the appropriate JSX to render the new sections. The following code is verbose, but this is simply applying the same component structure we already used for "Recent Orders."

**`Overview/index.tsx`**

```tsx {15-82} showLineNumbers={false} title="Overview/index.tsx"
export default function Overview() {
  ...

  return (
    <>
      <Grid
        ...
      >
        ...

        {getOrderPermission && (
        ...
        )}

        {getInvoicesPermission && (
        <Grid
          item
          key="recent-invoices"
          xs={12}
        >
          <Accordion
            onChange={(_e, isExpanded) => setInvoicesOpen(isExpanded)}
          >
            <AccordionSummary
              expandIcon={<ExpandMoreIcon />}
            >
              <Typography variant="h3">{b3Lang('overview.recentInvoices')}</Typography>
            </AccordionSummary>
            <AccordionDetails>
              <RecentInvoices
                startLoad={invoicesOpen}
              />
            </AccordionDetails>
          </Accordion>
        </Grid>
        )}

        {getShoppingListPermission && (
        <Grid
          item
          key="recent-shopping-lists"
          xs={12}
        >
          <Accordion
            onChange={(_e, isExpanded) => setShoppingListsOpen(isExpanded)}
          >
            <AccordionSummary
              expandIcon={<ExpandMoreIcon />}
            >
              <Typography variant="h3">{b3Lang('overview.recentShoppingLists')}</Typography>
            </AccordionSummary>
            <AccordionDetails>
              <RecentShoppingLists
                startLoad={shoppingListsOpen}
              />
            </AccordionDetails>
          </Accordion>
        </Grid>
        )}

        {getQuotesPermission && (
        <Grid
          item
          key="recent-quotes"
          xs={12}
        >
          <Accordion
            onChange={(_e, isExpanded) => setQuotesOpen(isExpanded)}
          >
            <AccordionSummary
              expandIcon={<ExpandMoreIcon />}
            >
              <Typography variant="h3">{b3Lang('overview.recentQuotes')}</Typography>
            </AccordionSummary>
            <AccordionDetails>
              <RecentQuotes
                startLoad={quotesOpen}
              />
            </AccordionDetails>
          </Accordion>
        </Grid>
        )}
      </Grid>
    </>
  );
}
```

**Your Overview page should now have fully functioning sections for all data types. Observe the GraphQL requests that are triggered by expanding each accordion.**

![Full Overview content](/_fern-img/3aaba3df9171844a218b8bdf62ec3d64904f6a27cd24e080187ca4c994e2bfaf.webp)

> **Info**
>
> [**View the Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-gql-02-pre...e-gql-02-post?diff=split)

## Full Exercise Code

> **Info**
>
> [**Full Exercise Code**](https://github.com/bigcommerce-edu/lab-b2b-buyer-portal/compare/e-gql-pre...e-gql-post?diff=split)

## Taking It Further

* Review the [B2B GraphQL API](https://api-b2b.bigcommerce.com/graphql/playground) or the native Buyer Portal code to explore the queries related to company users and addresses. Add summary information about these data types to the Overview page.

## Resources

* [Zod](https://zod.dev/)