This is the legacy documentation of Project-level Custom Applications, which is in maintenance mode. Visit the new documentation for Org-level Custom Applications.
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.graphqlquery FetchChannelQuery($id: String!, $locale: Locale) {channel(id: $id) {idversionkeyrolesname(locale: $locale)description(locale: $locale)createdAtlastModifiedAt}}
Then we use the Query
component of Apollo to send the query and render the result:
// channel-details.jsimport React from 'react';import { Query } from '@apollo/client/react/components';import Text from '@commercetools-uikit/text';import { GRAPHQL_TARGETS } from '@commercetools-frontend/constants';import { FetchChannelQuery } from './channels.graphql';const target = GRAPHQL_TARGETS.COMMERCETOOLS_PLATFORM;const ChannelDetails = props => (<Queryquery={FetchChannelQuery}variables={{id: props.channelId,locale: props.locale,}}context={{ target }}>{({ 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 {createApolloContextForProxyForwardTo,useMcQuery,} from '@commercetools-frontend/application-shell';import Text from '@commercetools-uikit/text';import HelloWorldQuery from './hello-world.graphql';const HelloWorld = () => {const { loading, data, error } = useMcQuery(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.jsimport { 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.jsimport React from 'react';import Text from '@commercetools-uikit/text';import { Sdk } from '@commercetools-frontend/sdk';import * as actions from './actions';const ChannelDetails = props => (<Sdk.GetactionCreator={() => 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.jsimport 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.