0

I'm developing a React component ClinicalViewsSummary that retrieves patient encounter data using the useSWR hook. The child component EncounterValuesTile fetches data with various params it has but sometimes receives undefined values, even when the params have not changed, and the data exists. Based on research, hooks should not do data fetching on the bottom level, but i cant avoid it in this case. As a result of looping, the hook is called multiple times before previous requests are resolved and some data is undefined

My main component looks like this :

const ClinicalViewsSummary: React.FC<OverviewListProps> = ({ patientUuid }) => {
 const config = useConfig();
 const { t } = useTranslation();
 const tilesDefinitions1 = config.tilesDefinitions;
 const tilesDefinitions = useMemo(
 () => [
 {
 tileHeader: 'characteristicsTitle',
 columns: [
 {
 id: 'currentRegimen',
 hasSummary: true,
 title: 'currentRegimenTitle',
 encounterType: 'e22e39fd-7db2-45e7-80f1-60fa0d5a4378',
 concept: '164515AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 summaryConcept: {
 primaryConcept: '164515AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 },
 },
 {
 id: 'artCohort',
 title: 'ArtCohortTitle',
 encounterType: 'e22e39fd-7db2-45e7-80f1-60fa0d5a4378',
 concept: '159599AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 isDate: true,
 conceptMappings: [
 '159599AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 '162572AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 '164516AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 '164431AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 '160738AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 ],
 },
 {
 id: 'dsdModel',
 title: 'dSDModelTitle',
 hasSummary: true,
 encounterType: 'e22e39fd-7db2-45e7-80f1-60fa0d5a4378',
 concept: 'dfbe256e-30ba-4033-837a-2e8477f2e7cd',
 summaryConcept: {
 primaryConcept: '166448AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 },
 },
 {
 id: 'populationType',
 title: 'populationTypeTitle',
 encounterType: 'e22e39fd-7db2-45e7-80f1-60fa0d5a4378',
 concept: '164431AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 hasSummary: true,
 summaryConcept: {
 primaryConcept: '166433AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 secondaryConcept: '2bf14240-b2b2-42b2-8cf3-b5f8a0cb7764',
 },
 },
 ],
 },
 {
 tileHeader: 'hivMonitoring',
 columns: [
 {
 id: 'viralLoad',
 title: 'currentViralLoad',
 encounterType: '3596fafb-6f6f-4396-8c87-6e63a0f1bd71',
 concept: '1305AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 hasSummary: true,
 summaryConcept: {
 primaryConcept: '163724AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 isDate: true,
 },
 },
 {
 id: 'lastCD4Count',
 title: 'lastCD4CountTitle',
 hasSummary: true,
 encounterType: '3596fafb-6f6f-4396-8c87-6e63a0f1bd71',
 concept: '5497AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 summaryConcept: {
 primaryConcept: '163724AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 isDate: true,
 },
 },
 ],
 },
 {
 tileHeader: 'lastVisitDetails',
 columns: [
 {
 id: 'nextAppointmentDate',
 title: 'nextAppointmentDateTitle',
 hasSummary: true,
 encounterType: 'cb0a65a7-0587-477e-89b9-cf2fd144f1d4',
 concept: '5096AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 summaryConcept: {
 primaryConcept: '5096AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 isDate: true,
 hasCalculatedDate: true,
 },
 },
 {
 id: 'programStatus',
 title: 'programStatusTitle',
 encounterType: 'a221448d-512b-4750-84bf-d29be9f802b3',
 concept: '163105AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
 },
 ],
 },
 ],
 [],
 );
 const tilesData = useMemo(
 () =>
 tilesDefinitions?.map((tile: any) => ({
 title: tile.tileHeader,
 columns: getEncounterTileColumns(tile, t),
 })),
 [],
 );
 return (
 <>
 {tilesData?.length > 0 &&
 tilesData?.map((tile, index) => (
 <MemoizedEncounterTile
 key={index}
 patientUuid={patientUuid}
 columns={tile.columns}
 headerTitle={tile.title}
 />
 ))}
 </>
 );
};

My child components that display columns in different tiles look like this :

const EncounterTile: React.FC<EncounterTileProps> = ({ patientUuid, columns, headerTitle }) => {
 return (
 <div className={styles.tileContainer}>
 <Tile className={styles.tile}>
 <div className={styles.cardTitle}>
 <h4 className={styles.title}> {headerTitle} </h4>
 </div>
 <Column className={styles.columnContainer}>
 {columns.map((column, ind) => (
 <EncounterValuesTile key={ind} patientUuid={patientUuid} column={column} />
 ))}
 </Column>
 </Tile>
 </div>
 );
};
export const MemoizedEncounterTile = React.memo(EncounterTile);
export const EncounterValuesTile: React.FC<EncounterValuesTileProps> = ({ patientUuid, column }) => {
 const { lastEncounter, isLoading, error, isValidating } = useLastEncounter(patientUuid, column.encounterUuid);
 if (isLoading || isValidating) {
 return <CodeSnippetSkeleton type="multi" data-testid="skeleton-text" />;
 }
 if (error || lastEncounter === undefined) {
 return (
 <div className={styles.tileBox}>
 <div className={styles.tileBoxColumn}>
 <span className={styles.tileTitle}> {column.header} </span>
 <span className={styles.tileValue}>--</span>
 </div>
 </div>
 );
 }
 return (
 <div className={styles.tileBox}>
 <div className={styles.tileBoxColumn}>
 <span className={styles.tileTitle}> {column.header} </span>
 <span className={styles.tileValue}>
 <LazyCell lazyValue={column.getObsValue(lastEncounter)} />
 </span>
 {column.hasSummary && (
 <span className={styles.tileTitle}>
 <LazyCell lazyValue={column.getSummaryObsValue(lastEncounter)} />
 </span>
 )}
 </div>
 </div>
 );
};

How can I ensure that these requests are made sequentially, so that I only fetch data when the previous fetch has completed? Is there a way to optimize my hooks to prevent multiple requests for the same encounter type? Are there best practices to handle this kind of situation in React when using hooks like useSWR? Any advice or solutions would be greatly appreciated!

Mosh Feu
29.5k18 gold badges94 silver badges142 bronze badges
asked Oct 9, 2024 at 12:11

1 Answer 1

1

You can refactor the code in a way that minimizes the number of times useLastEncounter is called, ensuring that it fetches the data efficiently and only once per encounter type. First, group the columns by their encounterType so that you can make one call to useLastEncounter for each unique encounterType.

const groupColumnsByEncounterType = (columns) => {
 return columns.reduce((acc, column) => {
 if (!acc[column.encounterType]) {
 acc[column.encounterType] = [];
 }
 acc[column.encounterType].push(column);
 return acc;
 }, {});
};

Refactor EncounterTile to Use the Grouped Columns. Fetch the encounter data once for each encounter type using useLastEncounter, and pass the data down to each column.

const EncounterTile: React.FC<EncounterTileProps> = ({ patientUuid, columns, headerTitle }) => {
 const columnsByEncounterType = useMemo(() => groupColumnsByEncounterType(columns), [columns]);
 return (
 <div className={styles.tileContainer}>
 <Tile className={styles.tile}>
 <div className={styles.cardTitle}>
 <h4 className={styles.title}>{headerTitle}</h4>
 </div>
 <Column className={styles.columnContainer}>
 {Object.entries(columnsByEncounterType).map(([encounterType, columns]) => (
 <EncounterData
 key={encounterType}
 patientUuid={patientUuid}
 encounterType={encounterType}
 columns={columns}
 />
 ))}
 </Column>
 </Tile>
 </div>
 );
};

Create a new EncounterData component that will fetch the data for each encounterType using useLastEncounter and pass it down to the columns.

const EncounterData: React.FC<{ patientUuid: string; encounterType: string; columns: any[] }> = ({ patientUuid, encounterType, columns }) => {
 const { lastEncounter, isLoading, error, isValidating } = useLastEncounter(patientUuid, encounterType);
 if (isLoading || isValidating) {
 return <CodeSnippetSkeleton type="multi" data-testid="skeleton-text" />;
 }
 if (error || lastEncounter === undefined) {
 return (
 <div className={styles.tileBox}>
 {columns.map((column, ind) => (
 <div key={ind} className={styles.tileBoxColumn}>
 <span className={styles.tileTitle}>{column.title}</span>
 <span className={styles.tileValue}>--</span>
 </div>
 ))}
 </div>
 );
 }
 return (
 <div className={styles.tileBox}>
 {columns.map((column, ind) => (
 <div key={ind} className={styles.tileBoxColumn}>
 <span className={styles.tileTitle}>{column.title}</span>
 <span className={styles.tileValue}>
 <LazyCell lazyValue={column.getObsValue(lastEncounter)} />
 </span>
 {column.hasSummary && (
 <span className={styles.tileTitle}>
 <LazyCell lazyValue={column.getSummaryObsValue(lastEncounter)} />
 </span>
 )}
 </div>
 ))}
 </div>
 );
};
answered Oct 11, 2024 at 9:41
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.