🚀Announcing Flightcontrol - Optimized Deployment for Fullstack Blitz.js and Next.js 🚀
Back to Documentation Menu

Using Queries

Topics

Jump to a Topic

In a React Component

To use one of your Blitz queries, call the useQuery hook with:

  1. Your query resolver
  2. The input arguments for your query function
  3. Optionally, a configuration object.

It returns this tuple array,[queryResult, queryExtras], where queryResult is exactly what's returned from your query function.

useQuery is built on react-query, so it automatically provides awesome features such as automatic caching and revalidation.

import { useQuery } from "blitz"
import getProject from "app/projects/queries/getProject"

function App() {
  const [project] = useQuery(getProject, { where: { id: 1 } })
}

For complete details, see the useQuery documentation.

🤔

You may be wondering how that can work since it's importing server code into your component: At build time, the direct function import is swapped out with a network call. So the query function code is never included in your client code.

Defaults to Keep in Mind

Out of the box, useQuery and the other query hooks are configured with aggressive but reasonable defaults. Sometimes these defaults can catch new users off guard or make learning/debugging difficult if they are unknown by the user.:

  • Query results that are currently rendered on the screen will become "stale" immediately after they are resolved and will be refetched automatically in the background when they are rendered or used again. To change this, you can alter the default staleTime for queries to something other than 0 milliseconds.
  • Query results that become unused (all instances of the query are unmounted) will still be cached in case they are used again for a default of 5 minutes before they are garbage collected. To change this, you can alter the default cacheTime for queries to something other than 1000 * 60 * 5 milliseconds.
  • Stale queries will automatically be refetched in the background when the browser window is refocused by the user or when the browser reconnects. You can disable this using the refetchOnWindowFocus and refetchOnReconnect options in queries.
  • Queries that fail in production because of a network error will silently and automatically be retried 3 times, with exponential backoff delay before capturing and displaying an error to the UI. To change this, you can alter the default retry and retryDelay options for queries.
  • Query results by default are deep compared to detect if data has actually changed and if not, the data reference remains unchanged to better help with value stabilization with regards to useMemo and useCallback. The default deep compare function use here (config.isDataEqual) only supports comparing JSON-compatible primitives. If you are dealing with any non-json compatible values in your query responses OR are seeing performance issues with the deep compare function, you should probably disable it (config.isDataEqual = () => false) or customize it to better fit your needs.

Options

See the useQuery documentation for a full list of possible options.

Dependent Queries

Dependent queries are queries that depend on previous ones to finish before they can execute. To do this, use the enabled option to tell a query when it is ready to turn on:

const [user] = useQuery(getUser, { where: { id: props.query.id } })
const [projects] = useQuery(getProjects, { where: { userId: user.id } }, { enabled: user }))

Pagination

Use the usePaginatedQuery hook

Infinite Loading

Use the useInfiniteQuery hook

Prefetching

All queries are automatically cached, so both of the following will cache the getProject query.

import { invoke } from "blitz"

const project = await invoke(getProject, { where: { id: props.id } })
<a onMouseEnter={() => invoke(getProject, { where: { id: props.id } })}>
  View Project
</a>

Blitz also supports prefetching multiple queries on the server and then dehydrating those queries to the internal query client. This allows Blitz to prerender markup that is immediately available on page load. Once JavaScript is available in the browser, Blitz will hydrate those queries and refetch them if they have become stale since the time they were rendered on the server.

The following example demonstrates how prefetching works in Blitz.

import {
  QueryClient,
  getQueryKey,
  useQuery,
  invokeWithMiddleware,
  dehydrate,
} from "blitz"

import getProject from "app/projects/queries/getProject"

export const getServerSideProps = async (context) => {
  const queryClient = new QueryClient()
  // IMPORTANT: the second argument to getQueryKey must exactly match the second argument to useQuery down below
  const queryKey = getQueryKey(getProject, {
    where: { id: context.params?.projectId },
  })
  await queryClient.prefetchQuery(queryKey, () =>
    invokeWithMiddleware(
      getProject,
      { where: { id: context.params?.projectId } },
      context
    )
  )

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  }
}

function ProjectPage() {
  const [project] = useQuery(getProject, { where: { id: props.id } })
  return <div>{project.name}</div>
}

export default ProjectPage

To ensure data is not shared between users and requests, a new query client is created for each page request. You can prefetch your data by invoking the prefetchQuery method on the newly-created client, passing in the query key and resolver along with any relevant input arguments. Once the data has been fetched, dehydrate the queries to the query client using the dehydrate method and pass the result to the page via the dehydratedState prop.

Note: if you want to prefetch an infinite query, you'll have to use prefetchInfiniteQuery instead of prefetchQuery and getInfiniteQueryKey instead of getQueryKey.

On the Server

getStaticProps

In getStaticProps, a query function can be called directly without useQuery

import getProject from "app/projects/queries/getProject"

export const getStaticProps = async (context) => {
  const project = await getProject({
    where: { id: context.params?.projectId },
  })
  return { props: { project } }
}

function ProjectPage({ project }) {
  return <div>{project.name}</div>
}

export default ProjectPage

getServerSideProps

In getServerSideProps, pass a query function to invokeWithMiddleware which will ensure appropriate middleware is run before/after your query function.

import {invokeWithMiddleware} from 'blitz'
import getProject from 'app/projects/queries/getProject'

export const getServerSideProps = async ({params, req, res}) => {
  const project = await invokeWithMiddleware(getProject, {where: {id: params.projectId}}, {req, res}))
  return {props: {project}}
}

function ProjectPage ({project}) {
  return <div>{project.name}</div>
}

export default ProjectPage

Idea for improving this page? Edit it on GitHub.