Performance
-
Use React’s built-in performance optimization hooks judiciously:
useMemofor expensive computations.useCallbackfor stable function references.memofor preventing unnecessary re-renders of complex components.
// Good - memoizing an expensive computation const sortedItems = useMemo(() => { return items .slice() .sort((a, b) => b.timestamp - a.timestamp); }, [items]); // Good - stable callback for child components const handleSubmit = useCallback((data: FormData) => { mutate(data); }, [mutate]); -
Use element optimization to avoid unnecessary re-renders. If you give React the same element reference, it will skip rendering the component.
-
Use the appropriate lifecycle registration method based on your component’s loading requirements:
- Use
getAsyncLifecyclefor modals, workspaces, secondary pages, and other components that can be loaded on demand.
// Good - modal loaded only when needed export const markPatientAliveModal = getAsyncLifecycle(() => import('./mark-patient-alive.modal'), options);- Use
getSyncLifecyclefor components that are already imported and need to be available immediately, such as lightweight links, action buttons, and root components that should not wait for another chunk:
// Good - component included in the main bundle export const startVisitForm = getSyncLifecycle(startVisitFormComponent, { featureName: 'start-visit-form', moduleName, });The key difference is where the component code lives.
getAsyncLifecycleuses a dynamic import, so the bundler can create a separate chunk that O3 loads only when the component is needed.getSyncLifecyclewraps a component that has already been statically imported, so it stays in the main module bundle and is available as soon as the frontend module loads. Avoid making every export async by default: many tiny dynamic chunks can add extra runtime requests. Read more about the difference between static and dynamic metadata here. - Use
-
Prefer icons and pictograms exported by
@openmrs/esm-frameworkwhen a matching OpenMRS asset exists. These come from the O3 styleguide sprite sheets and avoid pulling individual icons from@carbon/react/iconswhen the design system already provides the asset. -
Consider using prefetching to improve the responsiveness of your application. Prefetching can be particularly useful in these cases:
- Prefetching data for items that are likely to be accessed next:
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import { preload } from 'swr'; // Prefetch the next page of results const nextPageUrl = `${restBaseUrl}/patient?startIndex=${currentPage * pageSize}&limit=${pageSize}`; const prefetchNextPage = () => { void preload(nextPageUrl, openmrsFetch); };- Preloading data using SWR’s preload API when hovering over links or buttons:
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import { preload } from 'swr'; // Preload form schemas when hovering over links in the form builder <ConfigurableLink className={styles.link} to={editSchemaUrl} templateParams={{ formUuid: form?.uuid }} onMouseEnter={() => form?.uuid && void preload(`${restBaseUrl}/form/${form.uuid}?v=full`, openmrsFetch)} > {form.name} </ConfigurableLink>Remember that while prefetching can improve the user experience, it should be used judiciously to avoid unnecessary network requests and data usage.
-
Tweak your SWR configuration appropriately to optimize performance. O3 wraps frontend module components in an
openmrsComponentDecoratorthat provides a shared SWR cache,openmrsFetchas the default fetcher, and a global SWR configuration that disables most automatic revalidations. Because the SWRConfig component merges configurations from the parent context, you can override options in the global configuration on a per-component basis. Alternatively, you can specify a custom configuration at a per-hook level. Each SWR hook accepts an options object as an argument, which you can use to customize the behavior of the hook.// Simplified from the OpenMRS component decorator const defaultSwrConfig = { // max number of retries after requests have failed errorRetryCount: 3, // default fetcher function fetcher: openmrsFetch, // only revalidate once every 30 minutes focusThrottleInterval: 1800000, revalidateIfStale: true, // disable automatic revalidations by default revalidateOnFocus: false, revalidateOnReconnect: false, refreshInterval: 0, shouldRetryOnError: (error) => { if (error instanceof OpenmrsFetchError) { const status = error.response.status; if (status >= 500) { return true; } if (status === 401 || status === 403 || status === 429) { return true; } return false; } return true; }, }; <SWRConfig value={defaultSwrConfig}> <ComponentContext.Provider value={this.state.config}> {opts.disableTranslations ? ( <Comp {...this.props} /> ) : ( <I18nextProvider // props omitted for brevity > <Comp {...this.props} /> </I18nextProvider> )} </ComponentContext.Provider> </SWRConfig>// Component-level configuration using the SWRConfig component <SWRConfig value={{ revalidateOnFocus: true, revalidateOnReconnect: true }}> <Component /> </SWRConfig>// Custom configuration at a per-hook level const { data: response } = useOpenmrsSWR<PatientSearchResponse>('/ws/rest/v1/patient?v=full', { swrConfig: { revalidateOnFocus: true, revalidateOnReconnect: true, }, }); const patients = response?.data?.results ?? []; -
Avoid common performance pitfalls:
- Don’t create new objects or arrays in render when they are passed to memoized children.
- Avoid inline function definitions in JSX when they cause memoized children to rerender.
- Don’t use index as a key in lists whose order can change.
- Prevent unnecessary re-renders with clear state ownership and narrow component props.
// Bad - new function created every render <Button onClick={() => handleClick(id)} /> // Good - stable function reference const handleButtonClick = useCallback(() => { handleClick(id); }, [id, handleClick]); <Button onClick={handleButtonClick} /> -
Monitor your application’s performance using the React DevTools Profiler . To profile your app, run both your app and the O3 app shell locally, then use the import map overrides mechanism to load your app in the O3 app shell. Open the React DevTools Profiler in your browser and start a profiling session. Things to watch out for include:
- Components with frequent re-renders
- Components that are slow to render
- Long tasks in the main thread
- Components that could be memoized