Skip to main content

How to write a React component that fetches data from back-end?

In this guide, you will learn how to write a component that fetches data from the backend using a GraphQL API.

info

This part of the documentation assumes that you have already created the basic component structure. If you don't have any React component created, please follow the Example React component guide and create it.

Execute query and display data

Before you can fetch data for the component, you need to write a GraphQL query. For the purpose of this guide, we will assume that there is a todo object defined in the GraphQL schema and that you want to query for its id and name.

Create the following file with the query:

packages/webapp/src/shared/components/exampleComponent/example.graphql.ts
import { gql } from '@shipfast/webapp-api-client/graphql';

export const todoQuery = gql(/* GraphQL */ `
query todoQuery {
todo {
id
name
}
}
`);

Notice that it uses gql from the @shipfast/webapp-api-client/graphql package, which is a generated object based on the available schema.

Now you can modify the component file to use the above query and fetch the data:

packages/webapp/src/shared/components/exampleComponent/example.component.tsx
import { useQuery } from '@apollo/client';
import { FormattedMessage } from 'react-intl';

import { todoQuery } from './example.graphql';
import { Container } from './example.styles';

export const ExampleComponent = () => {
const { loading, data } = useQuery(todoQuery);
return (
<Container>
{!loading && data ? (
`Fetched todo: ${data.todo?.id} ${data.todo?.name}`
) : (
<FormattedMessage defaultMessage="Loading..." id="ExampleComponent / loading" />
)}
</Container>
);
};

The code above will use the useQuery hook from the Apollo library and display the data inside the component.

info

Note that because of how Apollo works, an XHR request with a query might not be fired in favor of cached data in its internal store.

Modify storybook

Changes made above will break storybook for ExampleComponent. You can address them by following these changes:

packages/webapp/src/shared/components/exampleComponent/example.stories.tsx
import { composeMockedQueryResult } from '@shipfast/webapp-api-client/tests/utils';
import { Story } from '@storybook/react';
import { append } from 'ramda';

import { withProviders } from '../../utils/storybook';
import { ExampleComponent } from './example.component';
import { todoQuery } from './example.graphql';

const Template: Story = () => {
return <ExampleComponent />;
};

const todo = { id: 1, name: 'Todo' };

export default {
title: 'Shared/ExampleComponent',
component: ExampleComponent,
decorators: [
withProviders({
apolloMocks: append(
composeMockedQueryResult(todoQuery, {
data: {
todo
}
})
),
}),
],
};

export const Default = Template.bind({});

The code above uses some internal ShipFast helpers that aid in writing Storybooks and tests efficiently:

  • composeMockedQueryResult: This is a helper that prepares a mocked query result for the given query object for the Apollo MockedProvider. You can read more about mocking queries here.
  • withProviders: This is a helper that wraps a Storybook story with the necessary providers (including MockedProvider).

The modified Storybook code will wrap every story in this file with the necessary providers that inject a mock query result, allowing the component to load data defined in the story.

Testing changes

Last thing to modify are the tests. Current test doesn't cover loading data and render on loading state. We would like to test if it displays correct data after query fetch with success. At the end of the test you can see assertions for loading text and first name that checks if they are rendered correctly.

note

For more information about testing loading state using Apollo please check the docs.

packages/webapp/src/shared/components/exampleComponent/__tests__/example.component.spec.tsx
import { composeMockedQueryResult } from '@shipfast/webapp-api-client/tests/utils';
import { screen } from '@testing-library/react';
import { append } from 'ramda';

import { render } from '../../../../tests/utils/rendering';
import { ExampleComponent } from '../example.component';

describe('ExampleComponent: Component', () => {
const Component = () => <ExampleComponent />;

it('should render user name', async () => {
const todo = { id: 1, name: 'Todo' };
const loadingText = 'Loading...';

render(<Component />, { apolloMocks: append(composeMockedQueryResult({
data: {
todo
}
}))});

expect(await screen.findByText(loadingText)).toBeInTheDocument();

expect(await screen.findByText(firstName)).toBeInTheDocument();
});
});
info

Note that this test example uses custom render method.

For more information about testing web application components, please visit the Writing tests for webapp section.

Working with collections

In the second part of this guide, you will learn how to fetch collections from a GraphQL API.

As in the previous part of this guide, we will assume that there is a collection of todos in the GraphQL schema and that the component will fetch a list of items from this collection.

Modify query

packages/webapp/src/shared/components/exampleComponent/example.graphql.ts
import { gql } from '@shipfast/webapp-api-client/graphql';

export const todosQuery = gql(/* GraphQL */ `
query todosQuery {
todos {
edges {
node {
id
name
}
}
}
}
`);

Modify the component

To display a collection of items, you can use the mapConnection helper. This helper is used to transform a GraphQL connection object into an array of objects.

packages/webapp/src/shared/components/exampleComponent/example.component.tsx
import { useQuery } from '@apollo/client';
import { mapConnection } from '@shipfast/webapp-core/utils/graphql';
import { FormattedMessage } from 'react-intl';

import { todosQuery } from './example.graphql';
import { Container } from './example.styles';

export const ExampleComponent = () => {
const { loading, data } = useQuery(todosQuery);
return (
<Container>
{!loading && data ? (
mapConnection((node) => <div key={node?.id}>{node?.name}</div>, data.todos)
) : (
<FormattedMessage defaultMessage="Loading..." id="ExampleComponent / loading" />
)}
</Container>
);
};

Modify storybook and tests

The last thing to do is to modify storybook and tests to handle collection of items:

packages/webapp/src/shared/components/exampleComponent/example.stories.tsx
import { composeMockedListQueryResult } from '@shipfast/webapp-api-client/tests/utils';
import { Story } from '@storybook/react';
import { append } from 'ramda';

import { withProviders } from '../../utils/storybook';
import { ExampleComponent } from './example.component';
import { todosQuery } from './example.graphql';

const Template: Story = () => {
return <ExampleComponent />;
};

const todos = [{ id: 1, name: 'Todo 1' }, { id: 2, name: 'Todo 2' }];

export default {
title: 'Shared/ExampleComponent',
component: ExampleComponent,
decorators: [
withProviders({
apolloMocks: append(
composeMockedListQueryResult(todosQuery,
'todos',
'todosQuery',
{
data: todos
})
),
}),
],
};

export const Default = Template.bind({});
packages/webapp/src/shared/components/exampleComponent/__tests__/example.component.spec.tsx
import { composeMockedListQueryResult } from '@shipfast/webapp-api-client/tests/utils';
import { screen } from '@testing-library/react';
import { append } from 'ramda';

import { render } from '../../../../tests/utils/rendering';
import { ExampleComponent } from '../example.component';

describe('ExampleComponent: Component', () => {
const Component = () => <ExampleComponent />;

it('should render list', async () => {
const todos = [{ id: 1, name: 'Todo 1' }, { id: 2, name: 'Todo 2' }];
const loadingText = 'Loading...';

render(<Component />, {
apolloMocks: append(
composeMockedListQueryResult(todosQuery, 'todos', 'todosQuery', { data: todos })
),
});

// check if the loaded is rendered
expect(await screen.findByText(loadingText)).toBeInTheDocument();

// next, check if the first name is rendered correctly
expect(await screen.findByText(todos[0].name)).toBeInTheDocument();
});
});