@lonerooftop/kitt-data-react
v4.8.3
Published
This package includes the glue between `kitt-data`, `kitt-ui` and your React app. It contains a number of convenient hooks for authenticating and connecting to the API, as well as some wrapper components.
Keywords
Readme
About
This package includes the glue between kitt-data, kitt-ui and your React app.
It contains a number of convenient hooks for authenticating and connecting to
the API, as well as some wrapper components.
Development
The development setup and dependencies are purposefully kept to the bare minimum. Notably, there's no Babel setup to transpile JSX into Javascript. Instead, components use React.createElement directly.
This package targets only greenfield browsers (Chrome, Edge, Firefox, Safari).
NOTE: for working with local kitt dependencies rather than the published NPM
packages, you might need to manually run npm link @lonerooftop/kitt-ui and
npm link @lonerooftop/kitt-data first.
Testing
We use Jest to write & run unit tests. Just run:
$ npm testto execute the tests.
Open issues with testing:
When testing hooks and rendering the test components the assertion checks the initial value/state of the hooks. setTimeout is used to wrap the assertion in order to check the right result of the hook. This needs to be checked and fixed when the right solution is found.
Components
Fetch
A wrapper around useFetch. Use this when you don't want to handle error or
loading state and just want to get the result back.
Props
- loaderProps: transparently passes props to
- See useFetch: method, query, transform, timeout, maxAge.
Returns
Fetch expects a function as child, which receives the fetch result.
Example
Simple query:
import { Fetch } from '@lonerooftop/kitt-data-react'
<Fetch method='getZones' query={{ filter_zones: 'metadata!=hide_bid' }}>
{(result) => {
console.log(result)
return null
}}
</Fetch>Multiple queries:
import { Fetch } from '@lonerooftop/kitt-data-react'
<Fetch
method='getZoneMetrics'
query={[
{ building_id: 'd3a259ba-72e2-4e0d-9968-d00bac604ba4', startdatetime: '2020-02-14T00:00:00.000+01:00', enddatetime: '2020-02-15T00:00:00.000+01:00' },
{ building_id: 'd3a259ba-72e2-4e0d-9968-d00bac604ba4', startdatetime: '2020-02-15T00:00:00.000+01:00', enddatetime: '2020-02-16T00:00:00.000+01:00' },
]}>
{([ resultA, resultB ]) => {
console.log(resultA, resultB)
return null
}}
</Fetch>Subscribe
A wrapper around useSocket that will show loader or error fallbacks.
Props
See useSocket: method, query, etc.
Returns
Fetch expects a function as child, which receives the fetch result.
Example
import { Subscribe } from '@lonerooftop/kitt-data-react'
<Subscribe method='getLatestZoneMetric' query={{ building_id: 'd3a259ba-72e2-4e0d-9968-d00bac604ba4' }}>
{({ state, connection, lastMessage }) => {
// whenever the state or lastMesage changes, this will be called again
console.log(state, connection, lastMessage)
return null
}}
</Fetch>TimedLoader
A wrapper that will show a spinner with self-updating message the longer it renders.
Props
- explanation: string|node, explanation text that will be shown
- inline: bool, wether to show an or component
Userdata
To fetch and store data on behalf of the user, add this wrapper to your app. It's a React context provider that returns the following value:
- error: error response from the server (e.g. when fetching or posting failed)
- loading: boolean
- preferences: JSON-parsed object. The JSON parsing is done by
getUserData. This is to work around the fact that the server only accepts strings for each value. - update: a function that you send your changed keys & values to.
There are two props that you can pass to the provider:
- maxAge: the duration in milliseconds that getUserData should be cached. You probably want to keep this value low (the default is 1 minute). This ensures that the whenever the user updates their preferences, we re-fetch a fresh copy of their preferences from the backend if the time between the last fetch was greater than maxAge.
- initialPreferences: optional object that contains any default keys
If you use this provider, you also want to look at its companion hooks.
Sending new or updated data
Setting or changing data is done with the update function. Its only parameter is
(a diff of) the data you want to send. Note that only simple key/value pairs are
accepted by the API. To send arrays, have a look at the useUserdataList
hook.
update({ foo: 'bar' })It's recommended to send only new or updated keys, they will be merged with the existing ones.
useUserdata
Use this hook to get access to any of the above keys from the Userdata context provider.
const UserdataConsumer = () => {
const { error, loading, preferences, update } = useUserdata()
// do stuff
return <div />
}- useUserdataList: this hook is to make it easier to work with arrays.
const UserdataListConsumer = () => {
const { list, has, add, remove } = useUserdataList('favorites')
return (
<ul>
{list.map(item =>
<li key={item}>
{item}
<button onClick={() => remove(item)}>Delete</button>
</li>
)}
</ul>
)
}Props
See useSocket: method, query, etc.
Returns
Fetch expects a function as child, which receives the fetch result.
Example
import { Subscribe } from '@lonerooftop/kitt-data-react'
<Subscribe method='getLatestZoneMetric' query={{ building_id: 'd3a259ba-72e2-4e0d-9968-d00bac604ba4' }}>
{({ state, connection, lastMessage }) => {
// whenever the state or lastMesage changes, this will be called again
console.log(state, connection, lastMessage)
return null
}}
</Fetch>Hooks
useFetch
A React hook that wraps around kitt-data's getFromPIE. Use this for fetching
from any supported API v4 method. Unless you have complex query requirements
(such as fetching from multiple different methods in parallel), this should be
your go-to hook for all query needs.
Parameters:
Pass an object with the following keys:
- method: string, a valid API method; see the API documentation. Required.
- query: query parameters valid for this API method. Accepts object, string or an instance of URLSearchParams. Additionally, you can pass an array of queries; this will trigger multiple parallel fetches. Optional depending on the method.
- transform: function that transforms the result (if successful). Optional, defaults to
(data) => data.result. - timeout: number (milliseconds) after which the request will be aborted. Optional, no default.
- maxAge: number (milliseconds) after which the cached response will be expired. Optional, defaults to 6 hours (6 * 60 * 60 * 1000).
- isAbortable: boolean, set to "true" by default. It enables the aborting the fetch requests
Example:
import { useFetch, FETCH_STATES } from '@lonerooftop/kitt-data-react'
function FetchZones ({ children, ...options }) {
let { error, result, state } = useFetch(options)
if (options.debug) {
console.debug({ error, result, state, ...options })
}
switch (state) {
case FETCH_STATES.ERROR:
case FETCH_STATES.ABORT:
return React.createElement(ErrorMessage, { error })
case FETCH_STATES.SUCCESS:
return children(result)
case FETCH_STATES.INITIAL:
case FETCH_STATES.FETCHING:
return React.createElement(TimedLoader, null)
default:
throw new Error('invalid state: ' + state)
}
}
Abortable fetch:
In order to reduce the number of duplicate requests or to cancel the request when navigating to another page we added the isAbortable property to useFetch hook. It enables the creation of the AbortController instance and aborts the fetch request automatically if the user navigates to another page or if the previous request is in progress and user triggers the new request. By default the feature is set to true. We can easily disable it by setting the property to false in the app. The AbortController is set as a part of state in reducer and can be access from the app if we want it.
useSocket
A React hook that sets up a Websocket connection to API methods that support it. It will try to keep the connection alive (using reconnecting-websocket underneath).
Parameters
- method: string, a valid API method; see the API documentation. Required.
- query: query parameters valid for this API method. Accepts object, string or an instance of URLSearchParams. Additionally, you can pass an array of queries; this will trigger multiple parallel fetches. Optional depending on the method.
- onOpen: function, callback that executes when the connection opens
- onMessage: function, callback that executes when a new message arrives
- onError: function, callback that executes when there's an error
- onClosed: function, callback that executes when the connection closed
See https://github.com/pladaria/reconnecting-websocket:
- maxReconnectionDelay?: number; // max delay in ms between reconnections
- minReconnectionDelay?: number; // min delay in ms between reconnections
- reconnectionDelayGrowFactor?: number; // how fast the reconnection delay grows
- minUptime?: number; // min time in ms to consider connection as stable
- connectionTimeout?: number; // retry connect if not connected after this time, in ms
- maxRetries?: number; // maximum number of retries
- maxEnqueuedMessages?: number; // maximum number of messages to buffer until reconnection
- startClosed?: boolean; // start websocket in CLOSED state, call
.reconnect()to connect - debug?: boolean; // enables debug output
Returns
- state: string, the connection state, one of
INITIAL,CONNECTING,OPEN,CLOSINGorCLOSED - connection: direct reference to the Websocket connection object
- lastMessage: the JSON-parsed result of the last message
Example
import { useSocket, WEBSOCKET_STATES } from '../hooks/useSocket'
export function useSocketHistory ({ children, ...options }) {
let socket = useSocket(options)
let [ log, updateLog ] = React.useState([])
React.useEffect(() => {
updateLog(log.concat(lastMessage))
}, [ lastMessage ])
return log
}