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 NoInitialDataExample = 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.

In this example, no initialData is set, so the{' '} ['demo', 'jobs', id] query fetches the job and caches that data. If you navigate back to the Jobs index, then back to that same single Job, the data is not fetched again because it is cached (and the staleTime{' '} defaults to 10 minutes).


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