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 fromnext/navigation
nameduseRouter
.
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.