Episode 25 connects the frontend to GraphQL with Apollo Client 3: setting up the client and ApolloProvider, the useQuery hook with polling and refetch, useMutation with optimistic UI, InMemoryCache and reactive variables management, and React integration patterns with custom hooks.

All the GraphQL server capabilities you've built now reach the most visible part: the frontend. Episode 25 connects your React app to GraphQL with Apollo Client 3, the most popular client library in the ecosystem. We'll build the client and provider, use the useQuery and useMutation hooks, manage the normalized cache, handle local state with reactive variables, and discuss good React integration patterns.
Install with npm install @apollo/client graphql, then configure the client with an HTTP link and a cache:
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
const client = new ApolloClient({
link: createHttpLink({ uri: "http://localhost:4000" }),
cache: new InMemoryCache(),
});import { ApolloProvider } from "@apollo/client";
function App() {
return (
<ApolloProvider client={client}>
<HomePage />
</ApolloProvider>
);
}const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
username
}
}
`;
function Profile({ userId }) {
const { data, loading, error } = useQuery(GET_USER, {
variables: { id: userId },
});
if (loading) return <p>Memuat...</p>;
if (error) return <p>Terjadi kesalahan: {error.message}</p>;
return <h1>{data.user.username}</h1>;
}function Dashboard() {
const { data, refetch } = useQuery(GET_STATS, { pollInterval: 5000 });
return (
<div>
<Stats data={data} />
<button onClick={() => refetch()}>Segarkan</button>
</div>
);
}const ADD_COMMENT = gql`
mutation AddComment($postId: ID!, $body: String!) {
addComment(postId: $postId, body: $body) {
id
}
}
`;
function CommentBox({ postId }) {
const [addComment, { error }] = useMutation(ADD_COMMENT, {
optimisticResponse: {
addComment: { id: "optimistic-1", body: "Sedang dikirim...", __typename: "Comment" },
},
});
return (
<form onSubmit={(e) => { e.preventDefault(); addComment({ variables: { postId, body: formBody } }); }}>
{error && <p>{error.message}</p>}
</form>
);
}const [login, { loading }] = useMutation(LOGIN, {
onCompleted: (data) => {
localStorage.setItem("token", data.login.token);
navigate("/dashboard");
},
});const cached = client.cache.readQuery({ query: GET_USER, variables: { id: "1" } });
client.cache.writeQuery({
query: GET_USER,
variables: { id: "1" },
data: { user: { ...cached.user, username: "nama-baru" } },
});This is useful for precise optimistic updates and cross-component synchronization.
export const cartItemsVar = makeVar([]);
export const addToCart = (product) => cartItemsVar([...cartItemsVar(), product]);Components using useReactiveVar(cartItemsVar) automatically re-render when its value changes — no Redux needed for simple local state.
function useUser(userId) {
return useQuery(GET_USER, { variables: { id: userId } });
}Combine with error boundaries to handle render failures, and use small, specialized components. Apollo Client supports React Suspense for data fetching with the <Suspense> component — a modern pattern you'll use again in episode 27.
Key takeaways:
useQuery provides loading, error, and data; refetch and polling for fresh data.useMutation with optimistic responses makes the UI feel instant.In the next episode, episode 26, you'll learn about alternative GraphQL clients — URQL with its exchanges system, Relay with fragment colocation and its compiler, the React Query and graphql-request combination, and a complete Apollo versus URQL versus Relay comparison. You'll be able to pick the right client for your needs!