Reputation: 165
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!
Upvotes: 0
Views: 43
Reputation: 69
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>
);
};
Upvotes: 1