import { inject, injectable, provide, useDependencies } from '@servicetitan/react-ioc'; import { observer } from 'mobx-react'; import { QueryApiStore } from '../utils/query-api.store'; import type { QueryApiOptions } from '../utils/query.api'; import { Fragment, useState } from 'react'; import { QueryClientStore } from '../utils/query-client.store'; import { makeAutoObservable } from 'mobx'; interface Job { id: number; title: string; description: string; } @injectable() class JobsNavigationStore { jobId: number | null = null; constructor() { makeAutoObservable(this); } navigateToJob = (id: number) => { this.jobId = id; }; navigateToJobs = () => { this.jobId = null; }; } @injectable() class JobsApi { async get(): Promise<{ data: Job[] }> { // eslint-disable-next-line no-console console.log('Fetching jobs...'); // Simulate an API call return new Promise(resolve => { setTimeout(() => { resolve({ data: [ { id: 1, title: 'Job 1', description: 'Description 1' }, { id: 2, title: 'Job 2', description: 'Description 2' }, ], }); }, 2000); }); } async getJob(id: number): Promise<{ data: Job }> { // eslint-disable-next-line no-console console.log(`Fetching job with id ${id}...`); // Simulate an API call return new Promise(resolve => { setTimeout(() => { resolve({ data: { id, title: `Job ${id}`, description: `Description ${id}` }, }); }, 2000); }); } } @injectable() class JobsStore extends QueryApiStore { @inject(JobsApi) protected api?: JobsApi; @inject(JobsNavigationStore) protected navigationStore?: JobsNavigationStore; get queryOptions(): QueryApiOptions { return { queryKey: ['demo', 'jobs'], queryFn: async () => (await this.api?.get())?.data || [], }; } } @injectable() class JobStore extends QueryApiStore { @inject(JobsApi) protected api?: JobsApi; @inject(JobsNavigationStore) protected navigationStore?: JobsNavigationStore; get queryOptions(): QueryApiOptions { const jobId = this.navigationStore?.jobId; return { queryKey: ['demo', 'jobs', jobId], queryFn: async () => (await this.api?.getJob(jobId!))?.data, enabled: Boolean(jobId), }; } } const Jobs = provide({ singletons: [JobsApi, JobsStore], })( observer(() => { const [{ navigateToJob }, { data, isFetching, isLoading }] = useDependencies( JobsNavigationStore, JobsStore ); return (

Jobs

isFetching: {isFetching ? 'true' : 'false'}

isLoading: {isLoading ? 'true' : 'false'}

    {data?.map(job => (
  • {job.description}

  • ))}
); }) ); const Job = provide({ singletons: [JobsApi, JobStore], })( observer(() => { const [{ navigateToJobs }, { data, isFetching, isLoading }] = useDependencies( JobsNavigationStore, JobStore ); return (

{data?.title}

isFetching: {isFetching ? 'true' : 'false'}

isLoading: {isLoading ? 'true' : 'false'}

Description: {data?.description}

); }) ); export const StaleTime10MinutesExample = provide({ singletons: [JobsNavigationStore, QueryClientStore], })( observer(() => { const [showApp, setShowApp] = useState(false); const [{ jobId }] = useDependencies(JobsNavigationStore); return (

In this example, a Jobs component is first rendered. The Jobs component uses a store with query key ['demo', 'jobs'] and a query to fetch jobs.

If a job is clicked, the Job component is rendered. The Job component uses a store with query key ['demo', 'jobs', id] and a query to fetch a single job.

staleTime defaults to 10 minutes, and controls how long before data is considered stale and needs to be refetched in the background. This uses a technique called "stale-while-revalidate" to ensure that cached data is available to display while it is being updated in the background.

In this example, staleTime is left as the default of 10 minutes. When you navigate back and forth between Jobs and a single Job, after each query has ran once, you will notice that isFetching is false , which means no fetch is occurring. This is because the data is still considered good for 10 minutes.


{!showApp && ( )} {showApp && (jobId ? : )}
); }) );