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), initialData: (() => { const jobs = this.queryClientStore?.queryClient.getQueryState([ 'jobs', ])?.data; return jobs?.find(j => j.id === 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 HasInitialDataExample = 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, {`queryOptions: { initialData }`} is set to the cached data for the corresponding job. This means that when a Job is rendered, no loading occurs because the cached data is already available. The default{' '} staleTime of 10 minutes is respected.


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