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 || [], staleTime: 0, }; } } @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), staleTime: 0, }; } } 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 StaleTime0Example = 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 set to 0. When you navigate back and forth between Jobs and a single Job, after each query has ran once, you will notice that isFetching will always be true as fetching occurs when navigating. Data is always considered to be stale immediately. However, you will notice that isLoading is false, because the stale cached data is still available to display.


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