Skip to content

Commit

Permalink
feat: add value-independent hook for setting online status message [L…
Browse files Browse the repository at this point in the history
…IBS-369] (#1363)

* feat: add value-indepent hook for setting online status message

* fix: export new hooks

* docs: custom status message

* fix: reinstate undefined; remove checks
  • Loading branch information
KaiVandivier authored Dec 12, 2023
1 parent 3253800 commit a2831e6
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 36 deletions.
46 changes: 46 additions & 0 deletions docs/advanced/offline/CustomOnlineStatusMessage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Custom Connection Status Message

By default, the header bar shows either an "Online" or "Offline" message based on whether the app can connect to the DHIS2 server. If it's useful for an app to share other relevant info in that badge, for example "3 changes saved locally", then it's possible to set a custom message there:

![Custom online status message](custom-online-status-message.png)

## Hooks

```ts
import {
useSetOnlineStatusMessage,
useOnlineStatusMessageValue,
useOnlineStatusMessage,
} from '@dhis2/app-runtime'
```

### `useSetOnlineStatusMessage`

It's most likely that you'll only use `useSetOnlineStatusMessage()`, a hook that returns a `setState` function. It can set the value in the Header Bar without causing a rerender for itself.

`setOnlineStatusMessage()` accepts a `ReactNode`-type object, so icons or other components can be added to the badge, but don't go crazy. A simple string is also a valid `ReactNode`:

```ts
const setOnlineStatusMessage: (message: ReactNode) => void =
useSetOnlineStatusMessage()
```

### `useOnlineStatusMessageValue`

This hook returns just the value:

```ts
const onlineStatusMessage: ReactNode = useOnlineStatusMessageValue()
```

### `useOnlineStatusMessage`

This hook returns both the value and the `set` function:

```ts
const { onlineMessage, setOnlineStatusMessage } = useOnlineStatusMessage()
```

## Example

You can see an example in use in the [Aggregate Data Entry app](https://github.com/dhis2/aggregate-data-entry-app/blob/dadd61392ea010a8017a19a25eaf76f885d9eea7/src/data-workspace/use-handle-headerbar-status.js)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/advanced/offline/useDhis2ConnectionStatus.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This hook is used to detect whether or not the app can connect to the DHIS2 serv

It's designed to detect server connection because checking just the internet connection can lead to problems on DHIS2 instances that are implemented offline, where features or actions might be blocked because the device is offline, even though the app can connect to the DHIS2 server just fine. In these cases, what matters is whether or not the app can connect to the DHIS2 server.

This what the DHIS2 Header Bar uses to show an "Online" or "Offline" badge.

```ts
import { useDhis2ConnectionStatus } from '@dhis2/app-runtime'

Expand Down
2 changes: 2 additions & 0 deletions runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export {
useOnlineStatus,
useDhis2ConnectionStatus,
useOnlineStatusMessage,
useSetOnlineStatusMessage,
useOnlineStatusMessageValue,
useCacheableSection,
CacheableSection,
useCachedSections,
Expand Down
6 changes: 5 additions & 1 deletion services/offline/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ export { CacheableSection, useCacheableSection } from './lib/cacheable-section'
export { useCachedSections } from './lib/cacheable-section-state'
// Use "useOnlineStatus" name for backwards compatibility
export { useNetworkStatus as useOnlineStatus } from './lib/network-status'
export { useOnlineStatusMessage } from './lib/online-status-message'
export {
useOnlineStatusMessage,
useSetOnlineStatusMessage,
useOnlineStatusMessageValue,
} from './lib/online-status-message'
export { clearSensitiveCaches } from './lib/clear-sensitive-caches'
export { useDhis2ConnectionStatus } from './lib/dhis2-connection-status'
60 changes: 34 additions & 26 deletions services/offline/src/lib/online-status-message.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,47 @@
import React, { ReactElement, ReactNode, useContext, useState } from 'react'
import { OnlineStatusMessageContextAPI } from '../types'

const defaultApi: OnlineStatusMessageContextAPI = {
onlineStatusMessage: undefined,
setOnlineStatusMessage: () => undefined,
}

const OnlineStatusMessageContext =
React.createContext<OnlineStatusMessageContextAPI>(defaultApi)

export const useOnlineStatusMessage = (): OnlineStatusMessageContextAPI => {
const { onlineStatusMessage, setOnlineStatusMessage } =
useContext<OnlineStatusMessageContextAPI>(OnlineStatusMessageContext)
type SetOnlineStatusMessage = (message: ReactNode) => void

return {
onlineStatusMessage,
setOnlineStatusMessage,
}
}
// 'get' and 'set' contexts are separated so 'setter' consumers that don't
// actually need the value don't have to rerender when the value changes:
const OnlineStatusMessageValueContext =
React.createContext<ReactNode>(undefined)
const SetOnlineStatusMessageContext =
React.createContext<SetOnlineStatusMessage>(() => undefined)

export const OnlineStatusMessageProvider = ({
children,
}: {
children: ReactNode
}): ReactElement => {
const [onlineStatusMessage, setOnlineStatusMessage] = useState<ReactNode>()
const [onlineStatusMessage, setOnlineStatusMessage] = useState<ReactNode>() // note: not undefined

return (
<OnlineStatusMessageContext.Provider
value={{
onlineStatusMessage,
setOnlineStatusMessage,
}}
>
{children}
</OnlineStatusMessageContext.Provider>
<OnlineStatusMessageValueContext.Provider value={onlineStatusMessage}>
<SetOnlineStatusMessageContext.Provider
value={setOnlineStatusMessage}
>
{children}
</SetOnlineStatusMessageContext.Provider>
</OnlineStatusMessageValueContext.Provider>
)
}

export const useOnlineStatusMessageValue = () => {
return useContext(OnlineStatusMessageValueContext)
}

export const useSetOnlineStatusMessage = () => {
return useContext(SetOnlineStatusMessageContext)
}

// combination of both getter and setter (also provides backward compatability)
export const useOnlineStatusMessage = () => {
const onlineStatusMessage = useOnlineStatusMessageValue()
const setOnlineStatusMessage = useSetOnlineStatusMessage()

return {
onlineStatusMessage,
setOnlineStatusMessage,
}
}
9 changes: 0 additions & 9 deletions services/offline/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ReactNode } from 'react'

// Cacheable Section types

export type RecordingState = 'default' | 'pending' | 'error' | 'recording'
Expand Down Expand Up @@ -66,10 +64,3 @@ export interface OfflineInterface {
getCachedSections: () => Promise<IndexedDBCachedSection[]>
removeSection: (id: string) => Promise<boolean>
}

// Online status types

export type OnlineStatusMessageContextAPI = {
onlineStatusMessage?: ReactNode
setOnlineStatusMessage: (additionalInfo: ReactNode) => void
}

0 comments on commit a2831e6

Please sign in to comment.