Skip to main content

Cacheable sections

This feature can only be used when PWA is enabled in d2.config.js. See the App Platform docs for more information.

The useCacheableSection hook and the CacheableSection component provide the controls for the section and the wrapper for the section, respectively. The useCachedSections hook returns a list of sections that are stored in the cache and a function that can delete them.

These features are supported by an <OfflineProvider> component which the app platform provides to the app.

To see a good example of these functions' APIs and their usage, see SectionWrapper.js in the PWA example app in the platform repository.

How it works

Cacheable sections enable sections of an app to be individually cached offline on demand. Using the CacheableSection wrapper and the useCacheableSection hook, when a user requests a section to be cached for offline use, the section's component tree will rerender, and the app's service worker will listen to all the network traffic for the component to cache it offline. To avoid caching that components' data before a user requests to do so, you can use the URL filters feature in d2.config.js.

Note that, without using these features, an app using offline caching will cache all the data that is requested by user as they use the app without needing to use cacheable sections.

Keep an eye out for this feature in use in the Dashboards app coming soon!

Usage

Wrap the component to be cached in a CacheableSection hook, providing an id and loadingMask prop. The loading mask should block the screen from interaction, and it will be rendered while the component is in 'recording mode'. Note that the useCacheableSection hook does not need to be used in the same component as the <CacheableSection>, they just need to use the same id. There is an example of this in the file linked below.

Here is an example of the basic usage:

import { CacheableSection, useCacheableSection } from '@dhis2/app-runtime'
import { Button, Layer, CenteredContent, CircularLoader } from '@dhis2/ui'
import { Dashboard } from './Dashboard'

/** An example loading mask */
const LoadingMask = () => (
<Layer translucent>
<CenteredContent>
<CircularLoader />
</CenteredContent>
</Layer>
)

export function CacheableSectionWrapper({ id }) {
const { startRecording, isCached, lastUpdated } = useCacheableSection(id)

return (
<div>
<p>{isCached ? `Last updated: ${lastUpdated}` : 'Not cached'}</p>
<Button onClick={startRecording}>
<CacheableSection id={id} loadingMask={<LoadingMask />}>
<Dashboard id={id} />
</CacheableSection>
</div>
)
}

CacheableSection API

PropTypeRequired?Description
childrenComponentYesSection that will be cached upon recording
idStringYesID of the section to be cached. Should match the ID used in the useCacheableSection hook.
loadingMaskComponentYesA UI mask that should block the screen from interaction. While the component is rerendering and recording, this mask will be rendered to block user interaction which may interfere with the recorded data.

useCacheableSection API

import { useCacheableSections } from '@dhis2/app-runtime'

function DemoComponent() {
const { startRecording, remove, lastUpdated, isCached, recordingState } =
useCacheableSection(id)
}

useCacheableSection takes an id parameter (a string) and returns an object with the following properties:

PropertyTypeDescription
startRecordingFunctionInitiates recording of a cacheable section's data for offline use. Causes a <CacheableSection> component with a matching ID to rerender with a loading mask based on the recording state to initiate the network requests. See the full API in the startRecording section below.
removeFunctionRemoves this section from offline storage. Returns a promise that resolves to true if the section was successfully removed or false if that section was not found in offline storage.
lastUpdatedDateA timestamp of the last time this section was successfully recorded.
isCachedBooleantrue if this section is in offline storage; Provided for convenience.
recordingStateStringOne of 'default', 'pending', 'recording', or 'error'. Under the hood, the CacheableSection component changes how its children are rendered based on the states. They are returned here in case an app wants to change UI or behavior based on the recording state.

startRecording API

The startRecording function returned by useCacheableSection returns a promise that resolves if 'start recording' signal is sent successfully or rejects if there is an error with the offline interface initiating recording. It accepts an options parameter with the following optional properties:

PropertyTypeDefaultDescription
onStartedFunctionA callback to be called once a recording has started and the service worker is listening to network requests. Receives no arguments
onCompletedFunctionA callback to be called when the recording has completed successfully. Receives no arguments
onErrorFunctionA callback to be called in the case of an error during recording. Receives an error object as an argument
recordingTimeoutDelayNumber1000The time (in ms) to wait after all pending network requests have finished before stopping a recording. If a user's device is slow, and there might be long pauses between requests that are necessary for that section to run offline, this number may need to be increased from its default to prevent recording from stopping prematurely.

Example:

import { useCacheableSection } from '@dhis2/app-runtime'
import { Button } from '@dhis2/ui'

function StartRecordingButton({ id }) {
const { startRecording } = useCacheableSection(id)

function handleStartRecording() {
startRecording({
onStarted: () => console.log('Recording started'),
onCompleted: () => console.log('Recording completed'),
onError: (err) => console.error(err),
recordingTimeoutDelay: 1000, // the default
})
.then(() => console.log('startRecording signal sent successfully'))
.catch((err) =>
console.error(`Error when starting recording: ${err}`)
)
}

return <Button onClick={handleStartRecording}>Save offline</Button>
}

useCachedSections API

The useCachedSections hook returns a list of all the sections that are cached, which can be useful if an app needs to manage that whole list at once. It takes no arguments and it returns an object with the following properties:

PropertyTypeDescription
cachedSectionsObjectAn object of cached sections' statuses, where the keys are the section IDs and the values are objects with a lastUpdated property that holds a Date object reflecting the time this section was last updated.
removeByIdFunctionReceives an id parameter and attempts to remove the section with that ID from the offline cache. If successful, updates the cached sections list. Returns a promise that resolves to true if that section is successfully removed or false if a section with that ID was not found.
syncCachedSectionsFunctionSyncs the list of cached sections with the list in IndexedDB. Returns a promise. This is handled by the removeById function and is probably not necessary to use in most applications.