Skip to content

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:

sh
npm install -D mearie
npm install @mearie/react
sh
yarn add -D mearie
yarn add @mearie/react
sh
pnpm add -D mearie
pnpm add @mearie/react
sh
bun add -D mearie
bun add @mearie/react
sh
deno add --dev npm:mearie
deno add npm:@mearie/react

Setup ​

1. Add Build Plugin ​

Add Mearie's build plugin to enable automatic type generation from your GraphQL documents:

typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import mearie from 'mearie/vite';

export default defineConfig({
  plugins: [react(), mearie()],
});
typescript
// 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:

typescript
// 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:

tsx
// 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:

tsx
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:

tsx
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:

tsx
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:

tsx
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:

tsx
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 ​