# Data fetching Learn about implementing data fetching requirements in your React components. In client-side applications, data fetching generally requires a lot of boilerplate code around implementation, state management, data normalization, etc. To make it easier to connect to platform APIs via the [Merchant Center API](https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api.md), we provide some pre-configured HTTP clients for both [GraphQL and REST APIs](https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api.md#api-gateway-endpoints). ## Access URL components To fetch data, you may need to parse the resource identifiers or parameters from the Merchant Center URL. We provide utilities to help you implement such functionality. When working with Custom Applications, you can use the [`useLocation`](https://v5.reactrouter.com/web/api/Hooks/uselocation) hook from the `react-router-dom` package to access the location data. For example, if you are implementing a Custom Application to manage Channels you may have a route for a details page: `/:channelId`. The full route would then be `/:projectKey/:entryPointUriPath/:channelId` and you can then access different path parameters, as follows. ```tsx title="Example to access path parameters from the URL" import { useLocation } from 'react-router-dom'; function ChannelsTable() { const location = useLocation(); const [_, projectKey, entryPointUriPath, channelId] = location.pathname.split('/'); // Use the channelId for data fetching return ( // Component rendering logic ) } ``` When working with Custom Views, you can use the [`useCustomViewContext`](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-application-shell-connectors.md#usecustomviewcontext) hook to get the [`hostUrl`](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-application-shell-connectors.md#hosturl) and use it to access the path and query parameters. For example, if you are implementing a Custom View to manage an Order, you can then access different path parameters, as follows. ```tsx title="Example to access path parameters from the URL" function OrdersInfo() { const hostUrl = useCustomViewContext((context) => context.hostUrl ); // https:////orders/111111-2222-3333-444-5555555555/general const url = new URL(hostUrl); console.log(url.pathname); // Logs "//orders/111111-2222-3333-444-5555555555/general" const orderId = url.pathname.split("/")[3]; // Use the orderId for data fetching return ( // Component rendering logic ) } ``` To simulate your Custom View as if it's rendered within a specific built-in application, you can override the default behavior for `hostUrl` to provide a custom URI path by setting the [`env.development.hostUriPath`](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/custom-view-config.md#envdevelopmenthosturipath). ## GraphQL HTTP client Commerce platform APIs and the Merchant Center have first-class support for GraphQL. We strongly recommend building your customization using GraphQL whenever possible. Customizations provide a pre-configured [Apollo GraphQL Client](https://www.apollographql.com/docs/react/) to perform requests to the GraphQL APIs. To create a custom instance of the Apollo Client, you can use the [`createApolloClient`](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-application-shell.md#createapolloclient) function. ### Use React hooks The preferred way of fetching data in a React component is to use [React hooks](https://react.dev/learn#using-hooks). The Apollo Client provides some React hooks to [query data](https://www.apollographql.com/docs/react/data/queries/) and [mutate data](https://www.apollographql.com/docs/react/data/mutations/). The following example shows how you can use hooks in your React component: ```js title="channels.js" highlightLines="5-9" import { useQuery } from '@apollo/client/react'; import { GRAPHQL_TARGETS } from '@commercetools-frontend/constants'; const Channels = (props) => { const { data } = useQuery(FetchChannelsQuery, { context: { target: GRAPHQL_TARGETS.COMMERCETOOLS_PLATFORM, }, }); return
; }; ``` In the preceding example, we define the `context.target` which configures the [GraphQL target for the Merchant Center API](https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api.md#graphql). The available GraphQL targets are exposed as a `GRAPHQL_TARGETS` constant in the [`@commercetools-frontend/constants`](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-constants.md) package. ### Use GraphQL documents Inside each customization, you define your queries and mutations in dedicated `.graphql` files that you can `import` in your component file. Using `.graphql` files is a great way to co-locate your data requirements next to your React component. Furthermore, you can leverage these files to improve the developer experience with editor syntax highlighting, linting, code generation, etc. Let's create one: ```graphql title="fetch-channels.ctp.graphql" query FetchChannels { channels { total count offset results { id key roles nameAllLocales { locale value } } } } ``` And import it from the React component file: ```js import FetchChannelsQuery from './fetch-channels.ctp.graphql'; ``` ### Error handling It's important to handle situations where requests may fail (network errors, schema validation errors, etc.). The Apollo Client hooks provide an `error` object that we can use in our component to handle failures. For example, we can render a content notification error instead of rendering the component. ```js title="channels.js" highlightLines="7,13-19" import { useQuery } from '@apollo/client/react'; import { GRAPHQL_TARGETS } from '@commercetools-frontend/constants'; import { ContentNotification } from '@commercetools-uikit/notifications'; import Text from '@commercetools-uikit/text'; const Channels = (props) => { const { data, error } = useQuery(FetchChannelsQuery, { context: { target: GRAPHQL_TARGETS.COMMERCETOOLS_PLATFORM, }, }); if (error) { return ( {getErrorMessage(error)} ); } return
; }; ``` For more information about using the `error` from Apollo Client, see [Error handling](https://www.apollographql.com/docs/react/data/error-handling/). In our case we just want to print the `error.graphQLErrors`. We can attempt to do that by implementing a helper function like this: ```js title="channels.js" const getErrorMessage = (error) => error.graphQLErrors?.map((e) => e.message).join('\n') || error.message; ``` ## REST HTTP client Some endpoints or APIs might not be available as GraphQL but as a standard HTTP REST endpoint instead. We provide a pre-configured declarative fetching library [`@commercetools-frontend/sdk`](https://www.npmjs.com/package/@commercetools-frontend/sdk), which builds on top of the [JS SDK client](https://commercetools.github.io/nodejs/sdk/api/sdkClient) and [Redux](https://redux.js.org/) The SDK library is built using Redux actions. This means that you dispatch an action describing the request and the library takes care of handling the request. ```js title="channels.js" import { useAsyncDispatch, actions } from '@commercetools-frontend/sdk'; import { MC_API_PROXY_TARGETS } from '@commercetools-frontend/constants'; const Channels = (props) => { const dispatch = useAsyncDispatch(); useEffect(() => { async function execute() { try { const result = await dispatch( actions.get({ mcApiProxyTarget: MC_API_PROXY_TARGETS.COMMERCETOOLS_PLATFORM, service: 'channels', }); ); // Update state with `result` } catch (error) { // Update state with `error` } } execute(); }, [dispatch]) return (
); }; ``` We define the `mcApiProxyTarget`, which is how you configure the proxy target for the [Merchant Center API](https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api.md#proxytarget). The available proxy targets are exposed as a `MC_API_PROXY_TARGETS` constant in the [`@commercetools-frontend/constants`](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-constants.md) package. The SDK library does not include features like data normalization, caching, etc. You will need to build these on your own. ## Custom HTTP client By default, we provide pre-configured HTTP clients for [GraphQL](https://docs.commercetools.com/merchant-center-customizations/development/data-fetching.md#graphql-http-client) and [REST](https://docs.commercetools.com/merchant-center-customizations/development/data-fetching.md#rest-http-client) API requests. However, you could use any other HTTP client of your choice, for example, [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), [Axios](https://github.com/axios/axios), [Stale-While-Revalidate (SWR)](https://swr.vercel.app/), etc. If you use another HTTP client, you need to configure these clients on your own. In particular, you'll need to specify the required [HTTP headers](https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api.md#http-headers) that should be sent with every request. To make it easier to configure your HTTP client with the necessary HTTP headers, we provide some dedicated utility functions, such as [executeHttpClientRequest](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-application-shell.md#executehttpclientrequest). ### Example using Fetch The following example uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). ```js highlightLines="8,15-26" import createHttpUserAgent from '@commercetools/http-user-agent'; import { buildApiUrl, executeHttpClientRequest, } from '@commercetools-frontend/application-shell'; const userAgent = createHttpUserAgent({ name: 'fetch-client', version: '1.0.0', libraryName: window.app.applicationName, contactEmail: 'support@my-company.com', }); const fetcher = async (url, config = {}) => { const data = await executeHttpClientRequest( async (options) => { const res = await fetch(buildApiUrl(url), options); const data = await res.json(); return { data, statusCode: res.status, getHeader: (key) => res.headers.get(key), }; }, { userAgent, headers: config.headers } ); return data; }; const Channels = () => { useEffect(() => { async function execute() { try { const result = await fetcher('/proxy/ctp/channels'); // Update state with `result` } catch (error) { // Update state with `error` } } execute(); }, []); // ... }; ``` ### Example using Axios The following example uses the [Axios](https://github.com/axios/axios) HTTP client. ```js highlightLines="9,16-31" import axios from 'axios'; import createHttpUserAgent from '@commercetools/http-user-agent'; import { buildApiUrl, executeHttpClientRequest, } from '@commercetools-frontend/application-shell'; const userAgent = createHttpUserAgent({ name: 'axios-client', version: '1.0.0', libraryName: window.app.applicationName, contactEmail: 'support@my-company.com', }); const fetcher = async (url, config = {}) => { const data = await executeHttpClientRequest( async (options) => { const res = await axios(buildApiUrl(url), { ...config, headers: options.headers, withCredentials: options.credentials === 'include', }); return { data: res.data, statusCode: res.status, getHeader: (key) => res.headers[key], }; }, { userAgent, headers: config.headers } ); return data; }; const Channels = () => { useEffect(() => { async function execute() { try { const result = await fetcher('/proxy/ctp/channels'); // Update state with `result` } catch (error) { // Update state with `error` } } execute(); }, []); // ... }; ``` ### Example using SWR The following example uses the [SWR](https://swr.vercel.app/) React hook. ```js highlightLines="9,16-27" import useSWR from 'swr'; import createHttpUserAgent from '@commercetools/http-user-agent'; import { buildApiUrl, executeHttpClientRequest, } from '@commercetools-frontend/application-shell'; const userAgent = createHttpUserAgent({ name: 'swr-client', version: '1.0.0', libraryName: window.app.applicationName, contactEmail: 'support@my-company.com', }); const fetcher = async (url, config = {}) => { const data = await executeHttpClientRequest( async (options) => { const res = await fetch(buildApiUrl(url), options); const data = await res.json(); return { data, statusCode: res.status, getHeader: (key) => res.headers.get(key), }; }, { userAgent, headers: config.headers } ); return data; }; const Channels = () => { const { data, error } = useSWR('/proxy/ctp/channels', fetcher); // ... }; ``` ### Example using Ky The following example uses the [Ky](https://github.com/sindresorhus/ky) HTTP client. ```js highlightLines="9,16-27" import ky from 'ky'; import createHttpUserAgent from '@commercetools/http-user-agent'; import { buildApiUrl, executeHttpClientRequest, } from '@commercetools-frontend/application-shell'; const userAgent = createHttpUserAgent({ name: 'ky-client', version: '1.0.0', libraryName: window.app.applicationName, contactEmail: 'support@my-company.com', }); const fetcher = async (url, config = {}) => { const data = await executeHttpClientRequest( async (options) => { const res = await ky(buildApiUrl(url), options); const data = await res.json(); return { data, statusCode: res.statusCode, getHeader: (key) => res.headers.get(key), }; }, { userAgent, headers: config.headers } ); return data; }; const Channels = () => { useEffect(() => { async function execute() { try { const result = await fetcher('/proxy/ctp/channels'); // Update state with `result` } catch (error) { // Update state with `error` } } execute(); }, []); // ... }; ``` ## Advanced usage ### Use Connector Hooks As your data requirements grow, for example, by having multiple queries and mutations in a React component, or using the same query in multiple components, you should consider extracting your queries into separate reusable hooks to help with organization and maintainability. We recommend extracting the logic into what we call connector hooks. A connector hook is a React hook that implements most of the data requirements (queries and mutations) specific to the use cases where the data should be used. For example, we can extract the fetching of Channels into a connector hook named `useChannelsFetcher`. This hook can be put into a file named `use-channels-connector.js`. ```js title="use-channels-connector.js" import { useQuery } from '@apollo/client/react'; import { GRAPHQL_TARGETS } from '@commercetools-frontend/constants'; export const useChannelsFetcher = () => { const { data, error, loading } = useQuery(FetchChannelsQuery, { context: { target: GRAPHQL_TARGETS.COMMERCETOOLS_PLATFORM, }, }); return { channels: data?.channels, error, loading, }; }; ``` Note that the `use-channels-connector.js` file can contain multiple connector hooks. For example `useChannelsFetcher`, `useChannelsUpdater`, `useChannelsCreator`, and so on. We would then use our connector hook in the `Channels` component instead of directly using the Apollo Client hooks. Therefore, there is less code in the React component as most of the logic and configuration is abstracted away in the connector hook. ```js title="channels.js" highlightLines="3,6" import { ContentNotification } from '@commercetools-uikit/notifications'; import Text from '@commercetools-uikit/text'; import { useChannelsFetcher } from '../../hooks/use-channels-connector'; const Channels = (props) => { const { channels, error } = useChannelsFetcher(); if (error) { return ( {getErrorMessage(error)} ); } return
; }; ``` ### Connect to an external API If your customization needs to connect to an [external API](https://docs.commercetools.com/merchant-center-customizations/concepts/integrate-with-your-own-api.md), in addition to the commerce platform APIs, the HTTP client has to be reconfigured to connect to the `/proxy/forward-to` endpoint with the appropriate headers. When using the [Apollo Client](https://docs.commercetools.com/merchant-center-customizations/development/data-fetching.md#graphql-http-client), you need to pass some configuration options to the `context` object. The `@commercetools-frontend/application-shell` package exposes a [createApolloContextForProxyForwardTo](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-application-shell.md#createapollocontextforproxyforwardto) to construct a predefined context object specific to the `/proxy/forward-to`. ```jsx highlightLines="2,10-12" 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 {data.title}; }; ``` Similarly, when [using a custom HTTP client](https://docs.commercetools.com/merchant-center-customizations/concepts/integrate-with-your-own-api.md#usage-for-custom-http-clients), you can pass an additional configuration object `forwardToConfig`. ```jsx highlightLines="21-23" import createHttpUserAgent from '@commercetools/http-user-agent'; import { buildApiUrl, executeHttpClientRequest, } from '@commercetools-frontend/application-shell'; const fetcherForwardTo = async (targetUrl, config = {}) => { const data = await executeHttpClientRequest( async (options) => { const res = await fetch(buildApiUrl('/proxy/forward-to'), options); const data = res.json(); return { data, statusCode: res.status, getHeader: (key) => res.headers.get(key), }; }, { userAgent, headers: config.headers, forwardToConfig: { uri: targetUrl, }, } ); return data; }; const HelloWorld = () => { useEffect(() => { async function execute() { try { const result = await fetcherForwardTo('https://my-custom-app.com/api'); // Update state with `result` } catch (error) { // Update state with `error` } } execute(); }, []); // ... }; ``` ### Call REST APIs inside your GraphQL queries This feature is available from version `21.10.0` onwards. If you want to keep using the benefits of GraphQL (and Apollo Client) but for REST APIs, you can use the [Apollo REST Link](https://www.apollographql.com/docs/react/v3/api/link/apollo-link-rest) as an opt-in feature to the [GraphQL HTTP client](https://docs.commercetools.com/merchant-center-customizations/development/data-fetching.md#graphql-http-client). You can follow the [instructions of the Apollo REST Link](https://www.apollographql.com/docs/react/v3/api/link/apollo-link-rest#quick-start) to set it up and configure the GraphQL document to send a request via the `@rest` directive. To use the Apollo REST Link with the built-in [GraphQL HTTP client](https://docs.commercetools.com/merchant-center-customizations/development/data-fetching.md#graphql-http-client), you need to create a new Apollo Client instance using the [`createApolloClient`](https://docs.commercetools.com/merchant-center-customizations/tooling-and-configuration/commercetools-frontend-application-shell.md#createapolloclient) utility function. An example of fetching Channels using the `@rest` directive: ```js title="channels.js" highlightLines="19-29" import { useMcQuery } from '@commercetools-frontend/application-shell'; import { useApplicationContext } from '@commercetools-frontend/application-shell-connectors'; import { MC_API_PROXY_TARGETS } from '@commercetools-frontend/constants'; import { usePaginationState, useDataTableSortingState, } from '@commercetools-uikit/hooks'; import { FetchChannelsQuery } from './fetch-channels.ctp.graphql'; const Channels = (props) => { const projectKey = useApplicationContext((context) => context.project.key); const { page, perPage } = usePaginationState(); const tableSorting = useDataTableSortingState({ key: 'key', order: 'asc' }); const searchParams = new URLSearchParams({ limit: perPage.value, offset: (page.value - 1) * perPage.value, sort: `${tableSorting.value.key} ${tableSorting.value.order}`, }); const { data, error, loading } = useMcQuery(FetchChannelsQuery, { fetchPolicy: 'cache-and-network', variables: { endpoint: `/proxy/${ MC_API_PROXY_TARGETS.COMMERCETOOLS_PLATFORM }/${projectKey}/channels?${searchParams.toString()}`, }, context: { skipGraphQlTargetCheck: true, }, }); if (error) { return ( {getErrorMessage(error)} ); } return
; }; ``` The related GraphQL document: ```graphql title="fetch-channels.ctp.graphql" query FetchChannelsRest($endpoint: String!) { channels @rest(type: "ChannelQueryResult", path: $endpoint) { total count offset results @type(name: "Channel") { id key nameAllLocales @type(name: "LocalizedString") { locale value } } } } ``` Pay attention to the following: - Types and nested types require you to define the `__typename` via `@rest(type: "")` or `@type(name: "")`. - The `endpoint` is passed as a variable and should point to the [Merchant Center API proxy target endpoint](https://docs.commercetools.com/merchant-center-customizations/concepts/merchant-center-api.md#proxytarget). - For `LocalizedString` fields such as `name` (in the REST API) you should use the GraphQL naming convention `nameAllLocales`. However, you also need to instruct the Apollo REST Link to correctly transform the shape of the field. You can do that using the [responseTransformer](https://www.apollographql.com/docs/react/v3/api/link/apollo-link-rest#response-transforming) function. ```js import { applyTransformedLocalizedStrings } from '@commercetools-frontend/l10n'; const restLink = new RestLink({ uri: getMcApiUrl(), // https://www.apollographql.com/docs/react/v3/api/link/apollo-link-rest#response-transforming responseTransformer: async (response, typeName) => { const data = await response.json(); switch (typeName) { case 'ChannelQueryResult': return { ...data, results: data.results.map((channel) => applyTransformedLocalizedStrings(channel, [ { from: 'name', to: 'nameAllLocales', }, ]) ), }; default: return data; } }, }); ``` ### Use file uploads If you need to support file uploads, you can use [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData). The `FormData` object can then be sent as the HTTP request body. It is important to properly configure the `Content-Type` HTTP header, which usually must be set to `multipart/form-data`. However, you also need to provide a `boundary` [directive](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#directives). We recommend that you omit the `Content-Type` HTTP header in the request and let the browser correctly infer it. When using the [`@commercetools-frontend/sdk`](https://www.npmjs.com/package/@commercetools-frontend/sdk) package, you need to explicitly unset the `Content-Type` HTTP header by passing `null` as value. ```js { 'Content-Type': null } ``` ## Related pages - [Section overview page](https://docs.commercetools.com/merchant-center-customizations.md) - [Previous page: Overview](https://docs.commercetools.com/merchant-center-customizations/development.md) - [Next page: UI components](https://docs.commercetools.com/merchant-center-customizations/development/ui-components.md)