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, you can't use the default Apollo Client.

As mentioned in the section above, the <ApplicationShell> has the Apollo Client pre-configured, which points to the /graphql endpoint of the Merchant Center API Gateway. The Apollo Client can only be configured to one URL, which means that for your external GraphQL API you need to create a new client.

To send requests to your GraphQL API you need to explicitly pass the client to each query or mutation, as otherwise Apollo will fall back to using the default client defined in the React Context, which is the pre-configured one.

// custom-apollo-client.js
import { createHttpLink } from 'apollo-link-http';
import { ApolloLink } from 'apollo-link';
import ApolloClient from 'apollo-client';
const httpLink = createHttpLink({
uri: 'https://my-custom-app.com/graphql',
headers: { accept: 'application/json' },
fetch,
});
const link = ApolloLink.from([
httpLink,
]);
const client = new ApolloClient({ link });
export default client;
// hello-world.js
import React from 'react';
import { useQuery } from 'react-apollo';
import Text from '@commercetools-uikit/text';
import customClient from './custom-apollo-client';
import HelloWorldQuery from './hello-world.graphql';
const HelloWorld = () => {
const { loading, data, error } = useQuery(HelloWorldQuery, {
client: customClient,
});
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.

Developer Center
HTTP APIGraphQL APIBETAPlatform Release NotesCustom ApplicationsBETASDKs & Client LibrariesImport & Export ToolsSUNRISE Starter FrontendsTutorialsFAQ
Merchant Center
DocumentationRelease Notes
Copyright © 2020 commercetools