Mutations and side effects
-
Use SWR’s bound mutate function and the
mutatefunction fromuseSWRConfig()when the user performs an action that changes backend state. This keeps O3’s shared SWR cache in sync and avoids reloading the page to see changes. PreferuseSWRConfig().mutateover importing the globalmutatefunction fromswrinside components, because O3 provides its own SWR cache through the component decorator.There are two main approaches to mutation with SWR:
- Using the bound mutate function returned by your resource hook:
import { restBaseUrl, useOpenmrsSWR } from '@openmrs/esm-framework'; // Using bound mutate to update a specific resource const patientUrl = `${restBaseUrl}/patient/${patientUuid}`; const { mutate } = useOpenmrsSWR<Patient>(patientUrl); const handleSave = async (updates: Partial<Patient>) => { try { await savePatient(updates); await mutate(); showSnackbar({ title: t('patientSaved', 'Patient saved successfully') }); await closeWorkspace({ discardUnsavedChanges: true }); } catch (error) { showSnackbar({ kind: 'error', title: t('errorSavingPatient', 'Error saving patient'), subtitle: error instanceof Error ? error.message : t('unknownError', 'Unknown error'), }); } };useOpenmrsSWR<T>cachesFetchResponse<T>, so only pass explicit data tomutatewhen you have the same response shape. Otherwise, revalidate the resource after a successful mutation.- Using
useSWRConfig().mutatefor broader cache updates:
import { restBaseUrl } from '@openmrs/esm-framework'; import { useSWRConfig } from 'swr'; const { mutate } = useSWRConfig(); // Update all cached visit resources that can show this deleted visit const handleDeleteVisit = async (patientUuid: string, visitUuid: string) => { try { await deleteVisit(visitUuid); // Update cached visit lists for this patient await mutate( (key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/visit?patient=${patientUuid}`), ); // Update the specific visit's data await mutate(`${restBaseUrl}/visit/${visitUuid}`); showSnackbar({ title: t('visitDeleted', 'Visit deleted successfully') }); } catch (error) { showSnackbar({ kind: 'error', title: t('errorDeletingVisit', 'Error deleting visit'), subtitle: error instanceof Error ? error.message : t('unknownError', 'Unknown error'), }); } }; -
When mutating data that appears in multiple places in your application, make sure to update all relevant cache entries. You can use the
mutatefunction with a key matcher to target specific cache entries. Here’s an example of updating cohort membership data:const getCohortMembershipUrl = (patientUuid: string) => `${restBaseUrl}/cohortm/cohortmember?patient=${patientUuid}&v=custom:(uuid,patient:ref,cohort:(uuid,name,startDate,endDate))`; // Memoized function to update cohort membership cache const useMutateCohortMembers = (patientUuid: string) => { const { mutate } = useSWRConfig(); return useCallback(() => { const key = getCohortMembershipUrl(patientUuid); return mutate( // Only mutate cache entries that exactly match this key (cacheKey) => typeof cacheKey === 'string' && cacheKey === key ); }, [patientUuid]); };And here’s an example of using the mutate hook in a submit handler:
const handleSubmit = useCallback(async () => { try { await Promise.all( selected.map(async (selectedId) => { const patientList = data.find((list) => list.id === selectedId); if (!patientList) { return; } try { await patientList.addPatient(); // Update the cohort membership data await mutateCohortMembers(); showSnackbar({ title: t('successfullyAdded', 'Successfully added'), kind: 'success', isLowContrast: true, subtitle: `${t('successAddPatientToList', 'Patient added to list')}: ${patientList.displayName}`, }); } catch { showSnackbar({ title: t('error', 'Error'), kind: 'error', subtitle: `${t('errorAddPatientToList', 'Patient not added to list')}: ${patientList.displayName}`, }); } }), ); } finally { closeModal(); } }, [data, selected, closeModal, t, mutateCohortMembers]);