import { useEffect } from 'react'; import { AllergyIntolerance } from 'fhir/r4'; import { useAllergyIntoleranceServiceContext, useFhirServiceContext, usePatientServiceContext, useWebSocketServiceContext } from '../services'; import { isArchived } from '../utils/archivedResource'; import { olderThan } from '../utils/versionOrder'; export const useFhirAllergyIntoleranceBroadcast = () => { const { subscribe, unsubscribe } = useWebSocketServiceContext(); const { setAllergyIntolerancesList, allergyFloor } = useAllergyIntoleranceServiceContext(); const { patientState } = usePatientServiceContext(); const { extractReferenceId } = useFhirServiceContext(); // THE NEWEST VERSION KNOWN OF EACH RECORD, KEPT THROUGH ITS REMOVAL - the service's, per patient, so its own answers // (an archive or delete acknowledged before its event) and these events both count. Without it a delayed older // event (the create echo, an earlier update) would put a removed row back. useEffect(() => { if (!patientState) { return; } const handleAllergyIntoleranceEvent = (cloudEvent: any) => { const allergyIntolerance: AllergyIntolerance = cloudEvent.data; // A deleted record does not come back, and nothing older than what is known of it is published. if (!allergyFloor.admits(allergyIntolerance)) return; if (cloudEvent.type === 'com.akashichealthrecords.fhir.allergyintolerance.deleted') allergyFloor.remove(allergyIntolerance, true); else if (isArchived(allergyIntolerance)) allergyFloor.remove(allergyIntolerance); else allergyFloor.learn(allergyIntolerance); // EVENTS CAN ARRIVE OUT OF ORDER. One older than the version the list holds (a delayed v4 after v5) is not // published - not as the row, and not as the archive that removes it - so an editor opening from the list // never starts from an older record than one already seen. const newest = (held: AllergyIntolerance) => (olderThan(allergyIntolerance, held) ? held : allergyIntolerance); const olderThanHeld = (list: AllergyIntolerance[]) => list.some((held) => held.id === allergyIntolerance.id && olderThan(allergyIntolerance, held)); const eventType = cloudEvent.type; const patientId = extractReferenceId(allergyIntolerance.patient); if (patientState.id === patientId) { switch (eventType) { case 'com.akashichealthrecords.fhir.allergyintolerance.created': // The list has two writers: this broadcast, and the service's own refetch after a write // (several of which POLL until the new id appears). Refetch first and the list already holds // the record, so an unconditional append put a SECOND copy of it on screen - observed on a // patient's questionnaire answers, where one submission rendered as two completed forms. // Keeping the broadcast's copy rather than skipping: its body is at least as fresh. setAllergyIntolerancesList(prev => prev.some(p => p.id === allergyIntolerance.id) ? prev.map(p => p.id === allergyIntolerance.id ? newest(p) : p) : [...prev, allergyIntolerance] ); break; case 'com.akashichealthrecords.fhir.allergyintolerance.updated': setAllergyIntolerancesList(prev => olderThanHeld(prev) ? prev : isArchived(allergyIntolerance) ? prev.filter(p => p.id !== allergyIntolerance.id) : prev.map(p => p.id === allergyIntolerance.id ? allergyIntolerance : p) ); break; case 'com.akashichealthrecords.fhir.allergyintolerance.deleted': setAllergyIntolerancesList(prev => prev.filter(p => p.id !== allergyIntolerance.id)); break; } } }; subscribe('com.akashichealthrecords.fhir.allergyintolerance.created', handleAllergyIntoleranceEvent); subscribe('com.akashichealthrecords.fhir.allergyintolerance.updated', handleAllergyIntoleranceEvent); subscribe('com.akashichealthrecords.fhir.allergyintolerance.deleted', handleAllergyIntoleranceEvent); return () => { unsubscribe('com.akashichealthrecords.fhir.allergyintolerance.created', handleAllergyIntoleranceEvent); unsubscribe('com.akashichealthrecords.fhir.allergyintolerance.updated', handleAllergyIntoleranceEvent); unsubscribe('com.akashichealthrecords.fhir.allergyintolerance.deleted', handleAllergyIntoleranceEvent); }; }, [patientState, subscribe, unsubscribe, extractReferenceId, allergyFloor]); };