Gene documentation
Services
React Query Services

Creating React Query Service

Generator

Generator code (opens in a new tab)

nx generate @brainly-gene/tools:service --name=myData --directory=my-feature/services --serviceType=react-query

Running this command will generate the following files:

CREATE libs/my-feature/services/myData-service/README.md
CREATE libs/my-feature/services/myData-service/.babelrc
CREATE libs/my-feature/services/myData-service/src/index.ts
CREATE libs/my-feature/services/myData-service/tsconfig.json
CREATE libs/my-feature/services/myData-service/tsconfig.lib.json
UPDATE tsconfig.base.json
UPDATE nx.json
CREATE libs/my-feature/services/myData-service/.eslintrc.json
CREATE libs/my-feature/services/myData-service/jest.config.js
CREATE libs/my-feature/services/myData-service/tsconfig.spec.json
CREATE libs/my-feature/services/myData-service/src/lib/queries.ts
CREATE libs/my-feature/services/myData-service/src/lib/useMyData.ts
CREATE libs/my-feature/services/myData-service/src/lib/useMyDataStatic.ts

This generator creates a React Query service in the specified directory and updates all NX configuration files accordingly. After generation, update the endpoint in the query function in queries.ts:

export type MyDataTypeAPI = {
  content: string; // SET UP YOUR API RESPONSE TYPE <-------
};
 
function queryFn(variables?: VariablesType, context?: QueryFunctionContext) {
  const url = variables?.url || 'https://jsonplaceholder.typicode.com/todos/1'; // CHANGE URL <------
  const fetchMethod = typeof window === 'undefined' ? nodeFetch : fetch;
  return reactQueryFetchWrapper<MyDataTypeAPI>(() => fetchMethod(url));
}

Usage in a Module

import { useMyData } from '@acme/my-feature/services/myData';
 
function MyModule() {
  const { data, loading, error } = useMyData();
 
  if (loading) {
    return <Spinner />;
  }
 
  if (error) {
    return <MyErrorUi message={error} />;
  }
 
  return <MyUIComponent data={data} />;
}

SSR Hydration

Next.js

To hydrate your query using server-side rendering, run the query on the page in getServerSideProps, then dehydrate and hydrate the client state:

Example page with SSR:

 
import {compose} from 'ramda';
import {getRequestHeaders} from '@brainly-gene/next';
import {getHomePageContainer} from '../ioc/getHomePageIoc';
import {GetServerSideProps} from 'next/types';
 
import {withIoc, reactQueryFactory} from '@brainly-gene/core';
 
import { QueryClient } from '@tanstack/react-query';
import { queryMyTest } from '@acme/services/my-test-service';
 
function HomePage() {
  return <div>Hello ExamplePage!</div>;
}
 
export const getServerSideProps: GetServerSideProps = async ({req}) => {
  const reactQueryClient = reactQueryFactory(() => new QueryClient());
  const reactQueryClientInstance = reactQueryClient.getClient();
 
  const reqHeaders = getRequestHeaders(req);
 
  // Invoke service queries here:
  await queryMyTest(reactQueryClientInstance);
 
  // End of queries invokes
 
  return {
    props: {
      dehydratedQueryClient: reactQueryClient.dehydrate(),
      reqHeaders,
    },
  };
};
 
export default compose(withIoc(getHomePageContainer))(HomePage);
 

Bind the Query client in the IOC container:

import {Container} from 'inversify';
import {getBaseContainer} from './baseIoc';
import {HomePagePropsType} from '../types/types';
import {ServiceTypes} from '@brainly-gene/core';
 
import {Factory, factory} from '@brainly-gene/core';
import {ReactQueryClientType} from '@brainly-gene/core';
 
export function getHomePageContainer(props?: HomePagePropsType) {
  const baseContainer = getBaseContainer();
 
  const queryClient =
    baseContainer.get<ReactQueryClientType>('reactQueryClient');
  queryClient.hydrate(props?.dehydratedQueryClient);
 
  const clients = {
    [ServiceTypes.reactQuery]: () => {
      return queryClient.getClient();
    },
  };
 
  const container = new Container();
  container.parent = baseContainer;
 
  container.bind<Factory>('serviceFactory').toFunction(factory(clients));
 
  return container;
}
 

Then in your service, you can use the injected React Query client:

import {queryFn, queryKey, VariablesType} from './queries';
import {
  transformReactQueryResponse,
  useInjectedReactQueryClient,
} from '@brainly-gene/core';
import {useQuery} from '@tanstack/react-query';
 
export function useMyTest(props?: {variables: VariablesType}) {
  const queryClient = useInjectedReactQueryClient();
 
  // useInfiniteQuery if paging is needed
  const result = useQuery(
    {
      queryKey: queryKey(props?.variables),
      queryFn: ctx => queryFn(props?.variables, ctx),
    },
    queryClient
  );
 
  return transformReactQueryResponse(result);
}
 

Stale Time

To avoid fetching data on the first render and utilize cached data from the server, set staleTime in getMyDataObservable within queries.ts. By default, staleTime is 0, which refetches queries on page load. Setting staleTime to 5000 milliseconds (5 seconds) is recommended to avoid double fetching. Setting it to Infinity prevents refetching altogether.

export function useMyData(props?: { variables: VariablesType }) {
  const queryClient = useInjectedReactQueryClient();
 
  // useInfiniteQuery if paging is needed
  const result = useQuery(
    {
      queryKey: queryKey(props?.variables),
      queryFn: (ctx) => queryFn(props?.variables, ctx),
      staleTime: 0,
    },
    queryClient
  );
 
  return transformReactQueryResponse(result);
}

Adding Query for an Existing Page

Once the above setup is ready, add your query to the server as follows:

export async function getServerSideProps() {
  const reactQueryClient = clients.getReactQueryClient();
  const reactQueryClientInstance = reactQueryClient.getClient();
 
  await Promise.all([
    queryMyData(),
    // ADD YOUR QUERY FUNCTION HERE <--------
  ]);
 
  return {
    props: {
      dehydratedQueryClient: reactQueryClient.dehydrate(),
    },
  };
}
⚠️

The URL and variables passed to queryFn must be consistent between the server and client. Cache keys depend on these elements. If they differ between client and server, hydration will not work and the query will be re-executed.

Pagination

To implement pagination, call fetchMore from the service API:

function MyModule() {
  const { data, loading, error, fetchMore } = useMyData({ page: 1 });
  const ref = useRef();
 
  useMediator('onNextPageButtonClick', (page) => fetchMore({ page }), ref);
 
  if (loading) {
    return <Spinner />;
  }
 
  if (error) {
    return <MyErrorUi message={error} />;
  }
 
  return <MyUIComponent data={data} />;
}

Update the service hook:

export function useMyData(props?: { variables: VariablesType }) {
  const queryClient = useInjectedReactQueryClient();
 
  const result = useInfiniteQuery( // Change useQuery to useInfiniteQuery
    {
      queryKey: queryKey(props?.variables),
      queryFn: (ctx) => queryFn(props?.variables, ctx),
      initialPageParam: 1,
      getNextPageParam: (lastPage, allPages) => {
        return 2; // Modify to return the next page number based on lastPage
      },
    },
    queryClient
  );
 
  return transformReactQueryResponse(result);
}