Documentation
Querying your API (client)
JavaScript frameworks
Next.js

Querying from Next.js

Fuse supports querying your API from Next.js's new App Router (including dedicated support for server components and server actions) and from the original Pages Router.

App router

Client components

When you are leveraging the use client directive in a /app component we have opted out of using server-components. This means we are going back to the traditional way of distributing our client over React.context.

It is advisable to create a Provider component with 'use client' that you use in your root-layout component so we are enabled to query data in any client page.

'use client'
 
import {
  Provider,
  createClient,
} from '@/fuse/client'
import React from 'react'
 
export const GraphQLClientProvider = (props: any) => {
  const [client, ssr] = React.useMemo(() => {
    const { client, ssr } = createClient({
      url: 'http://localhost:3000/api/fuse',
      // This is used during SSR to know when the data finishes loading
      suspense: true,
    })
 
    return [client, ssr]
  }, [])
 
  return (
    <Provider client={client} ssr={ssr}>
      <React.Suspense>
        {props.children}
      </React.Suspense>
    </Provider>
  )
}

Let's add this to app/layout.tsx so we are enabled to query data in any subsequent page. Querying data cna be done by using the useQuery hook from your generated fuse folder.

import { useQuery } from '@/fuse/client'
import { graphql } from '@/fuse'
import { AvatarFragment } from './Avatar'
 
const UserQuery = graphql(`
  query User ($id: ID!) {
    user(id: $id) {
      ...Avatar_UserFields
    }
  }
`, [AvatarFragment])
 
function User() {
  const [result] = useQuery({
    query: UserQuery,
    variables: { id: '1' },
  })
}

When you need to reach into your mutatation entry points we supply useMutation as well.

import { graphql } from '@/fuse'
import { useMutation } from '@/fuse/client'
import { AvatarFragment } from './Avatar'
 
const UpdateUser = graphql(`
  mutation UpdateUser ($id: ID!, firstName: $String!) {
    user(id: $id, firstName: $firstName) {
      ...Avatar_UserFields
    }
  }
`, [AvatarFragment])
 
const UpdateUser = () => {
  const [result, update] = useMutation(UpdateUser)
 
  return (
    <button onClick={() => update({ id: '1', firstName: 'John' })}>
      Update user
    </button>
  )
}

When you mutate data that is queried from a server-component you will need to call router.refresh() to re-render your server-component. The router is a hook exported from next/navigation named useRouter.

For data queried from client-components the client cache will recognise that data got altered and performa refetch. The cache matches this by means of the __typename property that is available on the data.

Heads up, when you query a list of items that is empty we won't be able to infer the __typename and you will need to supply it yourself.

import { useMemo } from 'react'
import { graphql } from '@/fuse'
import { useQuery } from '@/fuse/client'
import { AvatarFragment } from './Avatar'
 
const UserQuery = graphql(`
  query User ($id: ID!) {
    user(id: $id) {
      ...Avatar_UserFields
    }
  }
`, [AvatarFragment])
 
const User = () => {
  const [result] = useQuery({
    query: UserQuery,
    variables: { id: '1' },
    context: useMemo(() => ({ additionalTypenames: ['User'] }), []),
  })
}
 

Similar to the above we can perform mutations on the client as well by means of the useMutation hook, this hook is lazy, when we invoke it we'll get a function that we can invoke to execute it.

 
import { graphql } from '@/fuse'
import { useMutation } from '@/fuse/client'
import { redirect } from 'next/navigation'
 
const SayHello = graphql(`
  mutation Hello($name: String!) {
    sayHello(name: $name)
  }
`)
 
export async function Greet(args: { name: string }) {
  const [result, execute] = useMutation(SayHello)
 
  return <button onClick={() => execute({ name })}>Greet</button>
}

Server components

Server Components

When using React server components, Fuse skips the intermediate step of reaching out to an API endpoint and instead executes the GraphQL directly in the server component renderer:

import { graphql } from '@/fuse'
import { execute } from '@/fuse/server'
 
const UserQuery = graphql(`
  query User($id: ID!) {
    user(id: $id) {
      id
      name
      firstName
      avatarUrl
    }
  }
`)
 
export default async function Page() {
  const result = await execute({ query: UserQuery, variables: { id: '1' } })
}

This is the base way to query your components through server-components, you can now pass on the data to child components.

Server actions

We can also invoke mutations as part of a server-action (opens in a new tab) This requires us to create a new file, for example in this example we'll call a mutation with a name argument that returns us Hello ${args.name}.

We create actions/hello.ts and give it the content of

'use server'
 
import { graphql } from '@/fuse'
import { execute } from '@/fuse/server'
import { redirect } from 'next/navigation'
 
const SayHello = graphql(`
  mutation Hello($name: String!) {
    sayHello(name: $name)
  }
`)
 
export async function sayHello(args: { name: string }) {
  const result = await execute({ query: SayHello, variables: { name: args.name } })
 
  console.log(result.data?.sayHello)
 
  // After completing our mutation we perform a redirect
  redirect('/')
}

We can use this on the client by doing

import { sayHello } from './actions/sayHello'
 
const Component = ({ name }) => {
  const sayHelloFuse = sayHello.bind(undefined, { name: name || 'fuse' })
  return (
    <form action={sayHelloFuse}>
      <button type='submit'>Say hello and redirect to /</button>
    </form>
  )
}

Pages Router

Similar to the /app directory we can leverage useQuery the difference being that for server-side data we will query manually from getServerSideProps or getStaticProps and pass it into the component.

import {
  useQuery,
  withGraphQLClient,
  initGraphQLClient,
  ssrExchange,
  cacheExchange,
  fetchExchange,
} from '@/fuse/pages'
import { graphql } from '@/fuse'
import { AvatarFragment } from './Avatar'
 
const UserQuery = graphql(`
  query User ($id: ID!) {
    user(id: $id) {
      ...Avatar_UserFields
    }
  }
`, [AvatarFragment])
 
function User() {
  const [result] = useQuery({
    query: UserQuery,
    variables: { id: '1' },
  })
}
 
export async function getServerSideProps() {
  const ssrCache = ssrExchange({ isClient: false })
  const client = initGraphQLClient({
    url: 'http://localhost:3000/api/fuse',
    exchanges: [cacheExchange, ssrCache, fetchExchange],
  })
 
  await client.query(UserQuery, { id: '1' }).toPromise()
 
  const graphqlState = ssrCache.extractData()
 
  return {
    props: {
      graphqlState,
    },
  }
}
 
export default withGraphQLClient((ssrCache) => ({
  url: 'http://localhost:3000/api/fuse',
}))(Page)

Performing mutations is done in the same way as in the /app directory, with the same caveats.