Skip to Content
DocsRecipesStore values

Storing values

There are several forms of storage available in modern browsers. Here is a breakdown of them and when they should be preferred.

In-Memory

In general, in-memory storage refers to anything stored in JavaScript that is not part of persistent storage. Values stored in memory remain valid for:

TypeDuration
VariableVariable scope
React StateComponent’s lifecycle
React MemoComponent’s lifecycle
React ContextReact subtree lifecycle
Global Store (in-memory)Until page reload

No in-memory values survive page reload. For values that need to persist across a page reload, which means browser page refresh and not merely in-application navigation, use persistent storage or a session-storage-backed global store.

Variables

Simplest way to store a value. Looks something like:

const value = /* some value */;

The lifecycle of this variable depends on its scope . In general, it’s preferable to store values in the narrowest scope possible, that is, prefer function scope to module scope and module scope to global scope.

Inside React, it is generally preferable to use simple variables unless recomputing the variable is expensive. For example:

const name = `${patient.first} ${patient.last}`;

Is likely to be fine, unless operating on a large list of items or performing potentially complicated operations.

React State

const [value, setValue] = useState(/* some value */);

Sometimes we need variables which maintain consistent values across component renders. Where these values are component-specific, useState() allows us to store a component-specific version of a value that only gets updated when its setter is called.

React Memo

const value = useMemo(() => /* some computation */, [dependencies]);

Not exactly “storage”, but like useState() provides a consistent value until a condition holds (a value in the dependency array changes). Useful for infrequently computed or expensive to compute state.

React Context

React’s Context mechanism can be useful for storing data that needs to be shared solely by components in the same React tree; for example, values that are shared by multiple components in the same microfrontend might benefit from using React’s context. React Context provides values with a scope beyond just the lifecycle of a single component, but which are not maintained in the absence of the React tree. For example, a form might use React Context to store all the “current” values of all the input components on the form.

// in a parent module const MyContext = createContext(/* some value */); // inside a parent component return ( <MyContext.Provider value={/* some value */}>{children}</MyContext.Provider> ); // inside a child component const myValue = useContext(MyContext);

This is useful for ensuring that an entire React tree has a consistent version of state.

Global Store

Provided by @openmrs/esm-framework (specifically, esm-state); this gives us access to Zustand stores . This is useful for storing values that are shared across more than one component and need to be consistent across them. Note that while the underlying implementation uses Zustand, it is preferable to use the framework-provided APIs, such as createGlobalStore and useStore, e.g.,

import { createGlobalStore, useStore } from "@openmrs/esm-framework"; interface User { uuid: string; display: string; } interface UserStore { user: User | null; } // outside of a component const userStore = createGlobalStore<UserStore>("userStore", { user: null }); // inside a component const { user } = useStore(userStore); // at some other point userStore.setState({ user: /* value from somewhere */ }); // or using a functional update: userStore.setState((state) => ({ ...state, user: /* value from somewhere */ }));

Updating the store using setState() will update all variables subscribed to the store, including (like in the example) user.

Global stores are in memory by default. If a store needs to survive a browser refresh within the same tab session, pass "sessionStorage" as the third argument to createGlobalStore().

App context

App Context  is a mechanism for sharing state across different parts of the application. It is global by namespace, so any application can read a registered namespace, but the namespace is owned by the component that defines it and is removed when that component unmounts.

Common use cases for App Context include:

  • Sharing state between components that exist in different React trees
  • Managing temporary shared state that should only exist while specific components are active
  • Coordinating between different parts of a feature (e.g., sharing selected patient data across related components).

The framework provides two hooks for working with App Context:

Defining a context
const [dateRange, setDateRange] = useState<Date[]>([ dayjs().startOf("day").toDate(), new Date(), ]); useDefineAppContext<DateFilterContext>("laboratory-date-filter", { dateRange, setDateRange, });

useDefineAppContext() removes the namespace when the defining component unmounts. App context values should use null rather than undefined when they need to represent an empty value.

Accessing the context
const dateFilterContext = useAppContext<DateFilterContext>("laboratory-date-filter"); if (!dateFilterContext) { return null; } const { dateRange, setDateRange } = dateFilterContext;

When to use App Context vs Global Store

  • Use App Context when you need shared state that should exist only while its owning component is mounted.
  • Use Global Store when you need state to persist between component renders or across the entire application lifecycle.

Persistent Storage

Persistent storage refers to any storage mechanism that persists beyond a render of the single-page application.

The Backend

At least while online, storing persistent data to the backend should be the default. This is generally done through using the REST or FHIR APIs for OpenMRS.

Session Storage

This provides a Storage  API for values that persist across a page session. Page sessions generally persist as long as a single browser tab. This should be the preferred storage mechanism for values that need to persist across a page reload boundary.

Local Storage

This provides a Storage  API for values that persist until they are changed. They are specific to a web origin, but apply to all tabs and all pages. This is useful for storing user application-level preferences (e.g., language), but should not be used unless the value should be associated with the user-and-browser combination.

Avoid storing secrets or patient-identifying data in browser storage unless a feature explicitly requires it and the privacy tradeoff has been reviewed.

Last updated on