BETA

Data Fetching

In client-side applications, data fetching is not always easy and it usually involves a lot of boilerplate code around implementation, state management, data normalization, etc.

Since the commercetools platform has first-class support for GraphQL, we recommend to build Custom Applications using GraphQL as the main data fetching choice.

Requests to GraphQL APIs

To handle requests to the GraphQL APIs we use the built-in Apollo GraphQL Client.

The Apollo Client is already pre-configured within the <ApplicationShell> to connect to the /graphql endpoint of the Merchant Center API Gateway. You don't need to configure it on your own, unless you need to connect to an external GraphQL API (see Connecting to an external GraphQL API).

In the example below, we query for a Channel. First we define the GraphQL query in a .graphql file:

# channels.graphql
query FetchChannelQuery($id: String!, $locale: Locale) {
channel(id: $id) {
id
version
key
roles
name(locale: $locale)
description(locale: $locale)
createdAt
lastModifiedAt
}
}

Then we use the Query component of Apollo to send the query and render the result:

// channel-details.js
import React from 'react';
import { Query } from 'react-apollo';
import Text from '@commercetools-uikit/text';
import { GRAPHQL_TARGETS } from '@commercetools-frontend/constants';
import { FetchChannelQuery } from './channels.graphql';
const createQueryVariables = custom => ({
target: GRAPHQL_TARGETS.COMMERCETOOLS_PLATFORM,
...custom,
});
const ChannelDetails = props => (
<Query
query={FetchChannelQuery}
variables={createQueryVariables({
id: props.channelId,
locale: props.locale,
})}
>
{({ loading, error, data }) => {
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<Text.Headline as="h1">{`Channel: ${data.channel.name}`}</Text.Headline>
);
}}
</Query>
);
export default ChannelDetails;

That's it, Apollo will take care of data normalization, caching, etc.

Connecting to an external GraphQL API

In case your Custom Application needs to connect to an external GraphQL API, in addition to the commercetools GraphQL APIs, the Apollo Client needs to be reconfigured to connect to the /proxy/forward-to endpoint with the appropriate headers. This can be achieved by using the context option of Apollo Client, which allows to pass configuration options to the Apollo Links.

The @commercetools-frontend/application-shell package now exposes a createApolloContextForProxyForwardTo to construct a predefined context object specific to the /proxy/forward-to.

import React from 'react';
import { useQuery } from 'react-apollo';
import { createApolloContextForProxyForwardTo } from '@commercetools-frontend/application-shell';
import Text from '@commercetools-uikit/text';
import HelloWorldQuery from './hello-world.graphql';
const HelloWorld = () => {
const { loading, data, error } = useQuery(HelloWorldQuery, {
context: createApolloContextForProxyForwardTo({
uri: 'https://my-custom-app.com/graphql',
}),
});
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return <Text.Headline as="h1">{data.title}</Text.Headline>;
}

Requests to REST APIs

Some endpoints or APIs might not be available as GraphQL but as a standard HTTP REST endpoint instead.

To fetch the data, you can use any HTTP client of your choice, for example the fetch library. See authenticating requests.

However, commercetools provides a declarative fetching library @commercetools-frontend/sdk, which builds on top of the JS SDK client and Redux

The SDK library is already pre-configured within the <ApplicationShell>. You don't need to configure it on your own.

Fetching using the built-in SDK library

In the example below, we fetch a Channel from the commercetools platform HTTP API.

First we define the action creator:

// actions.js
import { actions as sdkActions } from '@commercetools-frontend/sdk';
import { MC_API_PROXY_TARGETS } from '@commercetools-frontend/constants';
const fetchChannelById = id =>
sdkActions.get({
mcApiProxyTarget: MC_API_PROXY_TARGETS.COMMERCETOOLS_PLATFORM,
service: 'channels',
options: { id },
});

To send the request and render the result, one option is using the <Sdk.Get> component:

// channel-details.js
import React from 'react';
import Text from '@commercetools-uikit/text';
import { Sdk } from '@commercetools-frontend/sdk';
import * as actions from './actions';
const ChannelDetails = props => (
<Sdk.Get
actionCreator={() => actions.fetchChannelById(props.channelId)}
render={({ isLoading, error, result }) => {
if (isLoading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return <Text.Headline as="h1">{`Channel: ${result.name}`}</Text.Headline>;
}}
/>
);
export default ChannelDetails;

Alternatively, you can dispatch and manage the action creator on your own if you don't want to use the <Sdk.Get> component:

// channel-details.js
import React from 'react';
import Text from '@commercetools-uikit/text';
import { useAsyncDispatch } from '@commercetools-frontend/sdk';
import { useShowApiErrorNotification } from '@commercetools-frontend/actions-global';
import * as actions from './actions';
const initialState = {
isLoading: true,
data: null,
error: null,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ok':
return { isLoading: false, data: action.payload, error: null };
case 'error':
return { isLoading: false, data: null, error: action.payload };
default:
return state;
}
};
const ChannelDetails = props => {
// The asyncDispatch is a wrapper around the redux dispatch and provides
// the correct return type definitions because the action resolves to a Promise.
const asyncDispatch = useAsyncDispatch();
const showApiErrorNotification = useShowApiErrorNotification()
const [state, dispatch] = React.useReducer(reducer, initialState);
React.useEffect(() => {
asyncDispatch(
actions.fetchChannelById(props.channelId)
)
.then(result => {
dispatch({ type: 'ok', payload: result });
})
.catch(error => {
dispatch({ type: 'error', payload: error });
showApiErrorNotification({ errors: error });
})
}, [props.channelId, asyncDispatch]);
if (state.isLoading) return 'Loading...';
if (state.error) return `Error! ${state.error.message}`;
return <Text.Headline as="h1">{`Channel: ${state.data.name}`}</Text.Headline>;
};
export default ChannelDetails;

The SDK library does not include features like data normalization, caching, etc. You will need to build those on your own. The playground application in the includes an example of setting up data normalization and caching.