Members
# constant MEDIUM_UPPER_BOUND
Width (in pixels) that represents the upper boundary (exclusive) of the medium
value
Since this is an exclusive boundary, this is the first width that is NOT considered medium.
Until doc auto renders, value is set to 1000
# constant NARROW_UPPER_BOUND
Width (in pixels) that represents the upper boundary (exclusive) of the narrow
value.
Since this is an exclusive boundary, this is the first width that is NOT considered narrow.
Until doc auto renders, value is set to 575
Methods
# useApi()
Custom Hook for retrieving a Spark API Interface
If you know the name and version of an API interface managed by the API Manager, you can use this hook to get an instance of that api interface.
import { useApi } from '@jcu/spark'
export default function MyComponent() {
// get version 1 of the vampire-information api interface
const vampireApi = useApi('vampires', '1')
// now you can call vampireApi.getInfo('dracula') to find out about Dracula.
}
# useApiFetch()
Custom hook for retrieving data from a Spark API Interface.
This hook consolidates some of the chore-work involved with
getting data from an API. As well as locating the API interface
and managing the data you're expecting back from the API call,
you have to handle the asynchronous nature of the fetch, do
something with potential errors, and make sure you can render
something valid and informative for the user at every moment.
useApiFetch
abstracts away most of this management stuff.
Basic usage
The simplest usage looks like this:
require { useApiFetch, FetchStatus } from '@jcu/spark'
// ...
const [errors, data, status] = useApiFetch(
'api-name', '1',
(api) => api.getSomeData()
)
// ...
if (status === FetchStatus.INIT) { return "...preparing..."}
if (status === FetchStatus.FETCH) { return "...loading..."}
if (status === FetchStatus.ERROR) {
console.log(errors)
return "Oh no! Errors!"
}
if (status === FetchStatus.DATA) {
return <InfoRenderer info={data} />
}
This usage of the hook has three arguments and creates three
const
variables.
The first two args are apiName and apiVersion. These are the name and version of your API Interface class, and are used by the hook to retrieve the interface from the ApiManager.
The third argument is a callback you write, that recieves an
instance of your requested API interface as the first argument,
and should make the api calls required to retrieve and then
return whatever data you need from the API. In the example, it's
calling an api interface function getSomeData()
and returning
whatever that returns. It's okay for this function to take some
time; the hook will arrange things so that it happens in the
background.
After running this code you've created three const
variables
in your component:
errors
* is an array of Error
objects describing problems
that occurred during the api call. If everything goes well, this
is zero length.
data
* will eventually hold the data returned by your
callback. It's initially undefined
.
status
* tells you what status the fetch is in, out of four
possible values. They're selected from the FetchStatus
enum.
The diagram below shows the four statuses and how your hook
instance can move between them. You should use the value of
status
to decide what gets rendered. When status
equals
FetchStatus.DATA
, then the data has arrived and you can show
it to the user.
Status Diagram
.--------------------.
| ERROR |
'--------------------'
A | deps change OR
| | user refresh
net fail | |
OR bad data | V
.------------------------------------.
| FETCHING |
'------------------------------------'
A A | good data
| | | arrives
deps | deps change OR | |
resolve | user refresh | V
.--------------. .--------------------.
| init | | HAS DATA |
'--------------' '--------------------'
Note that it's possible to be in the error state and still have data returned (for example, it's possible to add an error from your callback, but still return data); in that case, the state will be ERROR but your data variable will still include data returned by your callback.
Use other vars in API calls
You can include other variables, e.g. useState() vars you're tracking, directly in your API callback. This adds a dependency to the hook, so you will also have to give the hook a dependency array as a fourth argument.
const [hpMovieId, setHpMovieId] = getState(1)
// ...
const [errors, hpTitle, status] = useApiFetch(
'hp-movies', '1',
(api) => api.getMovieInfo('title', hpMovieId),
[hpMovieId]
)
Here the getMovieInfo()
api call needs the field to get
('title'
), and the movie id (initially 1
). So after the api
returns, we can expect data
to equal "Harry Potter and the Philosopher's Stone"
.
Since you included hpMovieId
in the dependency array, if that
variable changes, the hook will automatically re-run your API
fetch.
Don't fetch until everything's ready
If you need to hold off on your fetch until conditions are right,
you can supply a function to say when you're ready to go. To do
this provide an object as the fifth argument, with a ready
key
that's your function. Return false from this function if you
don't want to hit the API yet.
E.g. if you start a user's movie selection as null, you will want the user to choose a movie, then you can get the title.
const [hpMovieId, setHpMovieId] = getState(null)
// ...
const [errors, hpTitle, status] = useApiFetch(
'hp-movies', '1',
(api) => api.getMovieInfo('title', hpMovieId),
[hpMovieId],
{
ready: () => { hpMovieId !== null }
}
)
Now, no api call happens until you set the hpMovieId
to
something other than null -- and then, the hook notices the
change and automatically kicks off the fetch. Note that any
variable you refer to inside your ready
function must be
listed in your dependency array, so the hook knows when to check
again.
Other things you get when API-ing
Your API callback receives an API Interface instance as its first argument; you can accept a second argument to get access to some other stuff.
Also note that here, since I'm using await
in my callback, I
need to declare it as an async
function.
const [errors, hpTitle, status] = useApiFetch(
'hp-movies', '1',
async (api, controls) => {
// controls.logId is a fresh UUID you should use in your
// `X-JCU-Log-Id` header.
const myLogId = controls.logId
const result = await api.getMovieInfo('title', hpMovieId, myLogId)
// controls.addError(err) is how you signal that there
// was a problem. Give it a string or an `Error` if have
// one, but anything `.toString()`-ible is okay.
if (result.serverProblemReport) {
controls.addError(data.serverProblemReport)
} else {
return result
}
}
[hpMovieId]
)
Force a refresh
You hardly ever need to manually invoke an API refresh; React's ability to detect dependency changes means things will refresh whenever they need to. Sometimes though you know that server data has changed, and want to imperatively invoke a new data fetch.
To do that, add a fourth element to your array of const
s:
const [errors, hpTitle, status, refresh] = useApiFetch(
..then later, you can invoke refresh()
to force your api fetch
to happen. The hook still respects your ready
function, so
if your refresh isn't happening, check that you aren't passing
in a ready
that returns false.
# useEmbedded()
Custom Hook for determining if the app is "embedded" or not.
Embedded means you're displaying minimal headers and footers, in a manner suitable for this app to be included in an iframe within a parent page.
By default, apps are never "embedded" (even if they are in an
iframe). To be considered embedded, your app needs to set the
config.application.embedded
value to either "always"
, in
which case the app will be "embedded") or "auto"
, in which
case the app will be "embedded" when it is inside a frame or
iframe, and non-embedded if it is not.
The default value for config.application.embedded
is
"never"
.
import { useEmbedded } from '@jcu/spark'
export default function MyComponent() {
const embed = useEmbedded()
if (embed) {
return <b>I'm in an iframe with hardly any headers!</b>
} else {
return <i>Normal headers for me.</i>
}
}
Application headers and footers automatically adapt to
embedded-ness; you might like to use this hook to switch your
app's shape
prop from the default COLUMN
shape, which
constrains your content to a centered, reasonable width column,
to the WIDECOLUMN
shape, which uses the full width of the
app's viewport.
import {JcuApp, Shape, useEmbedded} from '@jcu/spark'
// ...
function App() {
const embed = useEmbedded()
return (
<JcuApp
title="Useful app"
shape={embed ? Shape.WIDECOLUMN : Shape.COLUMN}
>
<p>
This para will horizontally fill an iframe, but
be normal when it's in its own tab.
This para will horizontally fill an iframe, but
be normal when it's in its own tab.
This para will horizontally fill an iframe, but
be normal when it's in its own tab.
</p>
</JcuApp>
)
# useEnvironmentCheck() → {EnvironmentCheckReturn}
Custom hook for providing the environment value for the current app context.
The hook doesn't take any parameters and returns a set of booleans representing which environment state we are in.
Currently only two environments are supported: prod
and dev
.
The hook checks the value of process.env.NODE_ENV
during the build
process to determine what the current environment is. That means any
application built by Jenkins will report as prod
, even when hosted on
apps-test.jcu.edu.au
.
That means that the main use of this hook is to detect if the app is
running in the webpack dev server on a developer's machine (i.e. usually at
localhost:3000
).
Hook Usage
Checking the current environment
const {dev, prod} = useEnvironmentCheck()
...
if (dev) console.log("we are in dev mode")
# useIdentityCheck(attribute, values) → {IdentityCheckReturn}
Custom hook for checking the current OIDC user's identity attributes.
This hook tells you about identity attributes in the current user object.
You can supply either strings or regex objects to the lookup values of the hook. Strings will be matched completely
(i.e. x == y
) and regexes will use the built-in evaluation (i.e. regex.test(value)
).
The hook returns three booleans: exists
, any
, and all
exists
: The supplied attribute is present in the user object (e.g. optional attributes like groups)any
: At least one of the provided values match / is present in the attributeall
: All of the provided values match / are present in the attribute
Hook Usage Examples
Checking if the user has a specific groups
value (e.g. orgu-1440)
When a single value argument is supplied, the results for any
and all
will be the same.
const {any} = useIdentityCheck('groups', /orgu-1440/)
Checking if the user is in one or more of a set of groups
Multiple values can be provided to the hook to be evaluated collectively. In this case, any
will be true if any of
the supplied values match / are present in the supplied attribute and all
will only be true if all the supplied
values match / are present in the supplied attribute.
See below for gotchas about regexes and searching for multiple values.
const {all, any} = useIdentityCheck('groups', /144040/, /144060/)
Checking if the users first name is 'john' or contains a 'y'
This example is not likely to be a common use case but demonstrates the ability to check any piece of identity information.
const {any} = useIdentityCheck('given_name', 'john', /y/)
Regex matching
Because the matching portion of the hook evaluates supplied regexes using the built regex engine, you can provide "complex" regexes to match more specific patterns.
For the sake of demonstration, lets assume scrum masters have a specific orgu role assigned to their identity (e.g. orgu-144040-ScrumMaster) in addition to their standard staff orgu role (e.g. orgu-144040-Staff)
We can use a regex pattern to only allow access for someone who is a scrum master in orgu 144040 OR 144060.
const {all, any} = useIdentityCheck('groups', /1440(4|6).*scrum/i)
Note: The logic above can be confusing due to the regex having multiple "clauses". The any
and all
values of the
hook relate to how many of the provided values match, NOT whether the supplied value matches multiple attribute values.
In the above example, the all
value will be true if a user is in 144040 OR 144060 because the supplied value matches
an attribute value. If you want to check multiple conditions, they should be supplied as two separate argument values.
See this example
Parameters:
Name | Type | Description |
---|---|---|
attribute |
string | Which identity attribute to check |
values |
string | RegExp | Variable number of values to look for in |
# useJcuConfig()
Custom hook for access SPA config data.
This hook exposes the application configuration data.
The structure of the config data is clarified here
const config = useJcuConfig()
# useOidc() → {OidcHookReturn}
Custom hook for interacting with OIDC services.
All of the return values are wrappers to the axa useReactOidc hook.
The primary differences are:
oidcUser
has been renamed touser
for easier reference- the
login
function has additional functionality baked in (automatic recording of pre-login URL) - new
loggedIn
key to quickly determine if a user is logged in
Usage is the same as other hooks:
import {useOidc} from '@jcu/spark'
...
const {login, logout, user, loggedIn} = useOidc()
A pair of functions for logging in/out and the current OIDC user
# useScopeCheck(scopesopt) → {ScopeCheckReturn}
Custom hook for checking whether an OIDC user has the provided scopes.
The hook can accept zero or more strings that represent the requested scopes, and returns a set of booleans representing the results.
The three returned booleans are: loggedIn
, anyScope
, allScope
loggedIn
: Whether there is a user currently logged in (if no user is present, all return values are false)anyScope
: Whether the logged in user has ONE OR MORE of the provided scopesallScope
: Whether the logged in user has ALL of the provided scopes
The hook uses the ApiManager's templating engine to match provided scopes to the current environment. This means you can provide generic scopes (e.g. an API interface scope) and it will templated to the correct environment equivalent.
Hook Usage
Checking if a user has a specific scope
const {loggedIn, anyScope, allScope} = useScopeCheck('scope1')
A single scope string can be checked against the scopes of the logged in user.
In this use case anyScope
and allScope
are equivalent, a single scope will be true for both.
In practice, you will probably pull a scope from your ApiInterface subclass, and capture just the boolean result you're interested in. You can even rename your boolean as part of the destructure:
const {allScope: isAdmin} = useScopeCheck(myApiInterface.scopes.ADMIN)
Checking if a user has multiple scopes
const {loggedIn, anyScope, allScope} = useScopeCheck(['scope1','scope2'])
Pass an array of scope strings to check multiple scopes at once. In this
case anyScope
will be true if the user has at least one of the provided
scopes, and allScope
will be true if the user has every one of the
provided scopes.
An empty scopes array will result in false
s in allScope
and anyScope
.
Checking if a user is logged in
const {loggedIn} = useScopeCheck()
The hook can also be called without any arguments. Both allScope
and
anyScope
will be false, but loggedIn
is valid, so you can use this to
check that a user has logged in without checking any specific scopes.
Parameters:
Name | Type | Attributes | Description |
---|---|---|---|
scopes |
string | Array.<string> |
<optional> |
The scope(s) to check against the current user |
The results of checking the scope(s)
# useTimeCheck()
Custom hook for checking whether a provided time occurs before or after 'now'.
This hook is purely to support consistency in DisplayWhen's use of hooks.
This hook does not validation its props or do any other developer-friendly checking; it is intended to be used only by the DisplayWhen component.
We strongly recommend the date-fns library for doing time and date comparisons. Do NOT use this hook unless you REALLY know what you're doing.
# useWidthCheck() → {WidthCheckReturn}
Custom hook for classifying the current width of the app's
viewport into narrow
, medium
, and wide
categories.
The hook doesn't take any parameters and returns a boolean for each category.
narrow
: will betrue
if the screen width is below NARROW_UPPER_BOUND (exclusive)medium
: will betrue
if the screen width is above NARROW_UPPER_BOUND (inclusive) and below MEDIUM_UPPER_BOUND (exclusive)wide
: will betrue
if the screen width is above MEDIUM_UPPER_BOUND (inclusive)
Note that using this hook will re-render your component when the viewport width changes; that means your layout can adapt as the user resizes their browser or rotates their phone.
Hook Usage
Checking the current width
const {narrow, medium, wide} = useWidthCheck()
...
if (narrow) { return 'Your browser window is narrow like a phone' }
else if (medium) { return 'Your browser window is medium like a tablet (or you resized it)' }
else if (wide) { return 'Your browser window is on a nice wide screen' }
It's quick and easy to determine what the current screen width is and display accordingly.
Because they are all booleans, you can also use logic operations like:
const {narrow, medium} = useWidthCheck()
if (narrow || medium) { return 'not wide' }
const {narrow} = useWidthCheck()
if (!narrow) { return 'probably not a phone' }
Type Definitions
# EnvironmentCheckReturn
Properties:
Name | Type | Description |
---|---|---|
prod |
boolean | Whether we are currently in Production mode |
dev |
boolean | Whether we are currently in Development mode |
# IdentityCheckReturn
Properties:
Name | Type | Description |
---|---|---|
all |
boolean | All of the provided values match / are present in the attribute |
any |
boolean | At least one of the provided values match / is present in the attribute |
exists |
boolean | The supplied attribute is present in the user object (e.g. optional attributes like groups) |
# OidcHookReturn
Properties:
Name | Type | Description |
---|---|---|
login |
function | Function to trigger an OIDC log in |
logout |
function | Function to trigger an OIDC log out |
user |
any | An OIDC user object |
loggedIn |
boolean | Whether there is a currently logged in user |
events |
any | OIDC event object |
# ScopeCheckReturn
Properties:
Name | Type | Description |
---|---|---|
allScope |
boolean | Whether the logged in user has ALL of the provided scopes |
anyScope |
boolean | Whether the logged in user has ONE OR MORE of the provided scopes |
loggedIn |
boolean | Whether there is a user currently logged in (if no user is present, all return values are false) |
# WidthCheckReturn
Properties:
Name | Type | Description |
---|---|---|
wide |
boolean | Whether the screen width is below NARROW_UPPER_BOUND (exclusive) |
medium |
boolean | Whether the screen width is above NARROW_UPPER_BOUND (inclusive) and below MEDIUM_UPPER_BOUND (exclusive) |
narrow |
boolean | Whether the screen width is above MEDIUM_UPPER_BOUND (inclusive) |