React Integration
Mearie provides React hooks for queries, mutations, fragments, and subscriptions with full type safety and React Suspense support.
Installation
Install the core package and the React integration:
npm install -D mearie
npm install @mearie/reactyarn add -D mearie
yarn add @mearie/reactpnpm add -D mearie
pnpm add @mearie/reactbun add -D mearie
bun add @mearie/reactdeno add --dev npm:mearie
deno add npm:@mearie/reactSetup
1. Add Build Plugin
Add Mearie's build plugin to enable automatic type generation from your GraphQL documents:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import mearie from 'mearie/vite';
export default defineConfig({
plugins: [react(), mearie()],
});// next.config.js
import withMearie from 'mearie/next';
export default withMearie({
// Your Next.js config
});TIP
By default, Mearie looks for ./schema.graphql relative to your vite.config.ts or next.config.js. For custom schema locations or advanced configuration, see Codegen Config.
2. Create Client
Create a GraphQL client with your API endpoint. Import createClient and exchanges from @mearie/react:
// src/lib/graphql-client.ts
import { createClient, httpExchange, cacheExchange, dedupExchange } from '@mearie/react';
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [
dedupExchange(),
cacheExchange(),
httpExchange({
url: 'https://api.example.com/graphql',
}),
],
});See Exchanges for more details on available exchanges and middleware.
3. Set Up Provider
Wrap your app with the client provider to make the GraphQL client available throughout your component tree:
// src/app.tsx
import { ClientProvider } from '@mearie/react';
import { client } from './lib/graphql-client';
<ClientProvider client={client}>
<App />
</ClientProvider>;Hooks
useQuery
Fetch data with automatic caching and updates:
import { graphql } from '$mearie';
import { useQuery } from '@mearie/react';
export const UserProfile = ({ userId }: { userId: string }) => {
const { data, loading, error, refetch } = useQuery(
graphql(`
query GetUserQuery($id: ID!) {
user(id: $id) {
id
name
email
avatar
bio
age
}
}
`),
{
id: userId,
},
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<img src={data.user.avatar} alt={data.user.name} />
<h1>{data.user.name}</h1>
{data.user.bio && <p>{data.user.bio}</p>}
<p>Email: {data.user.email}</p>
<p>Age: {data.user.age}</p>
<button onClick={() => refetch()}>Refresh</button>
</div>
);
};previousData holds data from a previous set of variables — never an earlier value of the current ones. While a new set of variables loads, data is undefined but previousData still holds that earlier result, so data ?? previousData keeps the current view rendered instead of falling back to a loading state. See Data Ownership for the full contract.
Re-execution depends on the observer key (the document and its variables), skip, and the fetchPolicy value, each compared by value — so passing a fresh object literal for the same variables does not re-trigger the query, and changing any other option does not either. The client is a dependency too: unlike the other integrations, which capture it once at setup, useQuery re-executes if the ClientProvider value changes.
A variables change resolves within the same render — previousData and the initialData reseed are both computed during render — so there is no intermediate render in which the new variables read as unresolved.
useMutation
Modify data with automatic cache updates:
import { useState } from 'react';
import { graphql } from '$mearie';
import { useMutation } from '@mearie/react';
export const EditUserForm = ({ userId }: { userId: string }) => {
const [name, setName] = useState('');
const [updateUser, { loading }] = useMutation(
graphql(`
mutation UpdateUserMutation($id: ID!, $name: String!) {
updateUser(id: $id, input: { name: $name }) {
id
name
}
}
`),
);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
await updateUser({ id: userId, name });
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} required />
<button type="submit" disabled={loading}>
{loading ? 'Saving...' : 'Save'}
</button>
</form>
);
};useFragment
Co-locate data requirements with components:
import { graphql } from '$mearie';
import { useFragment } from '@mearie/react';
import type { UserCard_user$key } from '$mearie';
export const UserCard = ({ user }: { user: UserCard_user$key }) => {
const fragment = useFragment(
graphql(`
fragment UserCard_user on User {
id
name
avatar
email
}
`),
user,
);
return (
<div className="card">
<img src={fragment.data.avatar} alt={fragment.data.name} />
<h3>{fragment.data.name}</h3>
<p>{fragment.data.email}</p>
</div>
);
};useSubscription
Real-time updates via subscriptions:
import { graphql } from '$mearie';
import { useSubscription } from '@mearie/react';
interface ChatMessagesProps {
chatId: string;
}
export const ChatMessages = ({ chatId }: ChatMessagesProps) => {
const { data, loading } = useSubscription(
graphql(`
subscription MessageAddedSubscription($chatId: ID!) {
messageAdded(chatId: $chatId) {
id
body
author {
name
}
}
}
`),
{
chatId,
},
);
return (
<div>
<div>{loading ? 'Connecting...' : 'Connected'}</div>
{data?.messageAdded && (
<div>
<strong>{data.messageAdded.author.name}:</strong>
{data.messageAdded.body}
</div>
)}
</div>
);
};data holds the latest event only, and previousData holds the last event received under a previous set of variables.
React Suspense
Use with React Suspense for simpler loading states:
import { Suspense } from 'react';
import { graphql } from '$mearie';
import { useQuery } from '@mearie/react';
const UserProfile = ({ userId }: { userId: string }) => {
const { data } = useQuery(
graphql(`
query GetUserQuery($id: ID!) {
user(id: $id) {
id
name
email
}
}
`),
{ id: userId },
{ suspense: true },
);
return <h1>{data.user.name}</h1>;
};
const App = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId="123" />
</Suspense>
);
};Next Steps
- Queries - Learn more about queries
- Mutations - Learn more about mutations
- Fragments - Learn more about fragments
- Subscriptions - Learn more about subscriptions