Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to use GraphQL in React

Using GraphQL in React lets you request exactly the data your components need — no over-fetching, no under-fetching — with a single flexible endpoint. As the creator of CoreUI with 25 years of web development experience, I’ve used GraphQL in production React dashboards where the ability to query multiple data sources in one request dramatically reduced load times compared to multiple REST calls. For simple use cases, GraphQL works with a plain fetch call. For production apps with caching, optimistic updates, and subscriptions, Apollo Client is the standard choice. Both approaches are covered here so you can choose what fits your project.

Query a GraphQL API with plain fetch — no library needed.

// graphql.js
export async function graphqlFetch(query, variables = {}) {
  const res = await fetch('/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${localStorage.getItem('token')}`
    },
    body: JSON.stringify({ query, variables })
  })

  const { data, errors } = await res.json()
  if (errors) throw new Error(errors[0].message)
  return data
}
// In a component
import { useState, useEffect } from 'react'
import { graphqlFetch } from './graphql'

function UserList() {
  const [users, setUsers] = useState([])

  useEffect(() => {
    graphqlFetch(`
      query {
        users {
          id
          name
          email
        }
      }
    `).then(data => setUsers(data.users))
  }, [])

  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}

GraphQL always uses POST with a JSON body containing query and optionally variables. The response always has a data object (on success) and/or an errors array. A 200 status code does not mean the query succeeded — always check errors.

Apollo Client Setup

Install Apollo for caching and reactive updates.

npm install @apollo/client graphql
// apolloClient.js
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'
import { setContext } from '@apollo/client/link/context'

const httpLink = createHttpLink({ uri: '/graphql' })

const authLink = setContext((_, { headers }) => ({
  headers: {
    ...headers,
    authorization: `Bearer ${localStorage.getItem('token')}`
  }
}))

export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
})
// App.jsx
import { ApolloProvider } from '@apollo/client'
import { client } from './apolloClient'

export default function App() {
  return (
    <ApolloProvider client={client}>
      <UserList />
    </ApolloProvider>
  )
}

Using useQuery Hook

Apollo’s useQuery hook runs the query when the component mounts and provides loading and error states automatically.

import { useQuery, gql } from '@apollo/client'

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
      role
    }
  }
`

function UserList() {
  const { loading, error, data } = useQuery(GET_USERS)

  if (loading) return <p>Loading...</p>
  if (error) return <p>Error: {error.message}</p>

  return (
    <ul>
      {data.users.map(user => (
        <li key={user.id}>{user.name}  {user.role}</li>
      ))}
    </ul>
  )
}

useQuery re-runs the query when variables change, integrates with Apollo’s cache to avoid redundant network requests, and automatically updates the component when related mutations write new data.

Using useMutation Hook

import { useMutation, gql } from '@apollo/client'

const CREATE_USER = gql`
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      id
      name
    }
  }
`

function CreateUserForm() {
  const [createUser, { loading }] = useMutation(CREATE_USER, {
    refetchQueries: [{ query: GET_USERS }]
  })

  async function handleSubmit(name, email) {
    await createUser({ variables: { name, email } })
  }

  return <button onClick={() => handleSubmit('Alice', '[email protected]')} disabled={loading}>Create</button>
}

refetchQueries automatically re-fetches the users list after a successful mutation, keeping the UI in sync without manual cache manipulation.

Best Practice Note

This is the same GraphQL setup used in CoreUI React dashboard templates that connect to GraphQL APIs. For simple internal APIs, the plain fetch approach reduces bundle size and complexity. For complex apps with many queries, mutations, and subscriptions, Apollo Client’s normalized cache prevents stale data and reduces API calls. See how to use Apollo Client in React for advanced Apollo patterns including optimistic updates and subscriptions.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to loop through a 2D array in JavaScript
How to loop through a 2D array in JavaScript

How to loop inside React JSX
How to loop inside React JSX

How to limit items in a .map loop in JavaScript
How to limit items in a .map loop in JavaScript

How to return multiple values from a JavaScript function
How to return multiple values from a JavaScript function

Answers by CoreUI Core Team