Title

Global

Members

number

# 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

View Source tools/hooks/useWidthCheck.ts, line 25

number

# 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

View Source tools/hooks/useWidthCheck.ts, line 14

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.
}

View Source tools/hooks/useApi.ts, line 27

# 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 consts:

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.

View Source tools/hooks/useApiFetch.ts, line 220

# 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>
    )

View Source tools/hooks/useEmbedded.ts, line 69

# 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")

View Source tools/hooks/useEnvironmentCheck.ts, line 34

# 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 attribute
  • all: 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 attribute

View Source tools/hooks/useIdentityCheck.ts, line 80

# 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()

View Source services/ConfigManager.tsx, line 107

# 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 to user 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()

View Source tools/hooks/useOidc.ts, line 31

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 scopes
  • allScope: 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 falses 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

View Source tools/hooks/useScopeCheck.ts, line 61

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.

View Source tools/hooks/useTimeCheck.ts, line 22

# 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.

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' }

View Source tools/hooks/useWidthCheck.ts, line 74

Type Definitions

object

# EnvironmentCheckReturn

Properties:
Name Type Description
prod boolean

Whether we are currently in Production mode

dev boolean

Whether we are currently in Development mode

View Source tools/hooks/useEnvironmentCheck.ts, line 40

object

# 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)

View Source tools/hooks/useIdentityCheck.ts, line 136

object

# 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

View Source tools/hooks/useOidc.ts, line 74

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)

View Source tools/hooks/useScopeCheck.ts, line 104

object

# 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)

View Source tools/hooks/useWidthCheck.ts, line 96