What's New in v5

Version 5 is a from scratch rewrite. A lot of effort went into this release. If you find this package useful and have the means, please consider a small donation 🙂

The entire project has been rewritten in typescript for a more robust typescript experience. New issue and pull request templates have been added along with a contributing guide for anyone who would like to contribute to the project. Lastly this documentation site has been created to serve as a knowledge base for all things related to the IdleTimer project. If you have any ideas for sections, demos or tutorials to add feel free to start a discussion or open an issue.

If you are upgrading from v4 there are a few breaking changes that you should take into account. The rest of the API has not changed. Only new features and improvements to existing ones.

Breaking Changes#

Removal of <IdleTimer /> Component#

The major change in v5 is the removal of the IdleTimer component. It has been replaced with the withIdleTimer higher order component. You can use the higher order component to create the IdleTimer component if you wish and it would be a drop in replacement for the v4 component. A guide on how to do that can be found here.

Simplified Cross Tab API#

The Cross Tab feature has been rewritten from the ground up. It is now much smaller and more specialized for this particular use case. All the edge cases have been solved and the API surface has been reduced to the essentials.

In v4, the crossTab prop used to take a boolean or an object of configuration options. Now in v5, the crossTab prop only takes a boolean. This is how you enable or disable the Cross Tab Reconciliation feature. It is disabled by default.

All of the rest of the options from v4 have been set to sane defaults. This results in a more simple API surface and less type ambiguity. Events are emitted on all tabs by default and leader election has been reduced down to the leaderElection prop and the isLeader method. See more about leader election in the next section.

A new property has been added called syncTimers. It takes a number that represents the throttle value in milliseconds for the sync function. Every user action that is defined in the events array will be replicated to all tabs, thus keeping the timeouts across tabs roughly in sync. By default this property has a value of 0 which is equivalent to disabled. Set it to the maximum amount of drift between timeouts that is tolerable for you project. Setting it too low will cause a lot of messaging between tabs. A good place to start is around 200.

In v4 the following code...

// Hook
const idleTimer = useIdleTimer({
crossTab: {
type: undefined,
channelName: 'idle-timer',
fallbackInterval: 2000,
responseTime: 100,
removeTimeout: 1000 * 60,
emitOnAllTabs: true
}
})
// Component
<IdleTimer crossTab={{
type: undefined,
channelName: 'idle-timer',
fallbackInterval: 2000,
responseTime: 100,
removeTimeout: 1000 * 60,
emitOnAllTabs: false
}} />

Becomes this in v5...

// Hook
const idleTimer = useIdleTimer({
crossTab: true
})
// Higher Order Component Wrapped
<IdleTimer crossTab />

With the new syncTimers feature:

// Hook
const idleTimer = useIdleTimer({
crossTab: true,
syncTimers: 200
})
// Higher Order Component Wrapped
<IdleTimer crossTab syncTimers={200} />

If you are using multiple IdleTimer instances on the same page, you can use the name prop to isolate crossTab events to their respective instances.

// Hook
const logoutTimer = useIdleTimer({
timeout: 1000 * 60 * 30,
crossTab: true,
syncTimers: 200,
name: 'logout-timer'
})
const activityTimer = useIdleTimer({
timeout: 1000 * 60 * 5,
crossTab: true,
syncTimers: 200,
name: 'activity-timer'
})
// Higher Order Component Wrapped
<IdleTimer crossTab timeout={1000 * 60 * 30} syncTimers={200} name='logout-timer' />
<IdleTimer crossTab timeout={1000 * 60 * 5} syncTimers={200} name='activity-timer' />

There is also a new method getTabId() that will return the current tabs id that is used internally by IdleTimer's crossTab feature.

Leader Election#

In v4 Leader Election was enabled by default and would automatically emit events on only the leader tab. In v5, leader election has to be enabled manually by setting the leaderElection property. In order to emit events only on the leader, you can use the isLeader method inside your event handlers.

import { useIdleTimer } from 'react-idle-timer'
const App = () => {
const { isLeader } = useIdleTimer({
crossTab: true,
leaderElection: true,
onIdle: () => {
if (isLeader()) {
// I am the leader, perform remote action
} else {
// I am not the leader, perform local action
}
}
})
return <></>
}

Last Active and Idle Times#

The getLastActiveTime() and getLastIdleTime() methods now return Date objects instead of a timestamp. This will eliminate the need to cast to a Date before formatting. If you want to get the old timestamp you can just call getLastActiveTime().getTime() and getLastIdleTime().getTime().

Non Breaking Changes#

Removal of passive and capture Props#

The passive and capture props have been removed and are automatically set to true. There is no good reason a user would want to set either of them to false and as such, they have been removed to reduce complexity and API surface. If you were to leave these props set in your useIdleTimer hook or withIdleTimer higher order component, they wouldn't do anything. This is not necessarily a breaking change but something that is good to be aware of while migrating.

New Features#

Web Worker Timers#

When a window is backgrounded, the browser will throttle timers in an attempt to save on CPU cycles. This can cause IdleTimer to produce delayed and inaccurate results. To combat this, the timers interface has been exposed as a property and an implementation of WebWorker thread timers are exposed in order to keep them alive when a window is throttled by the browser. The worker is provided via a blob URL and this can cause strict CSP rules to be violated. It is strongly suggested that you update your rule to allow the worker to run so the timers are accurate. If this is not an option, you can omit the timers property and use the default main thread timers. Main thread timers are the default in order to allow projects that wish to omit worker thread timers to tree shake out the worker code. If you decide to enable worker timers, take a look at Testing Considerations for information on how to mock these timers for your test suites.

import { useIdleTimer, workerTimers } from 'react-idle-timer'
export const App = () => {
const idleTimer = useIdleTimer({ timers: workerTimers })
return (
<h1>Using web worker timers!</h1>
)
}

Integrated Prompting#

A common use case for IdleTimer is to detect when your user is idle and then open a modal to ask the user if they are still there. There have been a lot of questions about how to do this, so in v5, the IdleTimer will handle it for you.

The new promptTimeout configuration option along with the onPrompt callback and isPrompted() state getter is all you need to prompt your user before going idle.

Here is a minimal example. More complete examples can be found in the features section.

export function App () {
// Set timeout values
const timeout = 1000 * 60 * 30
const promptTimeout = 1000 * 30
// Modal open state
const [open, setOpen] = useState(false)
// Time before idle
const [remaining, setRemaining] = useState(0)
const onPrompt = () => {
// onPrompt will be called after the timeout value is reached
// In this case 30 minutes. Here you can open your prompt.
// All events are disabled while the prompt is active.
// If the user wishes to stay active, call the `reset()` method.
// You can get the remaining prompt time with the `getRemainingTime()` method,
setOpen(true)
setRemaining(promptTimeout)
}
const onIdle = () => {
// onIdle will be called after the promptTimeout is reached.
// In this case 30 seconds. Here you can close your prompt and
// perform what ever idle action you want such as log out your user.
// Events will be rebound as long as `stopOnMount` is not set.
setOpen(false)
setRemaining(0)
}
const onActive = () => {
// onActive will only be called if `reset()` is called while `isPrompted()`
// is true. Here you will also want to close your modal and perform
// any active actions.
setOpen(false)
setRemaining(0)
}
const { getRemainingTime, isPrompted, activate } = useIdleTimer({
timeout,
promptTimeout,
onPrompt,
onIdle,
onActive
})
const handleStillHere = () => {
setOpen(false)
activate()
}
useEffect(() => {
const interval = setInterval(() => {
if (isPrompted()) {
setRemaining(Math.ceil(getRemainingTime() / 1000))
}
}, 1000)
return () => {
clearInterval(interval)
}
}, [getRemainingTime, isPrompted])
return (
<div className='modal' style={{ display: open ? 'block' : 'none' }}>
<p>Logging you out in {remaining} seconds</p>
<button onClick={handleStillHere}>Im Still Here</button>
</div>
)
}

Cross Tab Messaging#

IdleTimer now exposes its cross tab messaging layer so you can easily broadcast arbitrary messages to all instances of your page running in all tabs. This is exposed by two new APIs...

a message callback:

onMessage?: (data: string | number | object) => void

and a message emitter:

message (data: string | number | object, emitOnSelf?: boolean): void

Using these mechanisms, you can use IdleTimer to send messages to all instances of your application. Here is a basic example with redux.

import { useIdleTimer } from 'react-idle-timer'
import { useDispatch } from 'react-redux'
import { logAction } from './Actions'
export function App () {
// Action dispatcher (redux)
const dispatch = useDispatch()
// Message handler
const onMessage = data => {
switch (data.action) {
case 'LOGOUT_USER':
dispatch(logoutAction())
break
// More actions
default:
// no op
}
}
// IdleTimer instance
const { message } = useIdleTimer({ onMessage })
// Logout button click
const onLogoutClick = () => {
// Tell all tabs to log the user out.
// Passing true as a second parameter will
// also emit the event in this tab.
message({ action: 'LOGOUT_USER' }, true)
}
return (
<button onClick={onLogoutClick}>Logout</button>
)
}

More complete examples can be found in the features section.

Immediate Events#

A new configuration option called immediateEvents has been added.

immediateEvents?: IEventsType[]

Any events added to immediateEvents will immediately switch the user into an idle or prompted state, calling onIdle or onPrompt respectively. This is useful for events such as pagehide, where you want to bypass the timeout and go directly to an idle state.

If an event is set in both events and immediateEvents, the immediate event will take precedence allowing you to override the default events if you like.

Higher Order Component#

With the removal of the IdleTimer component, the withIdleTimer higher order component is now the default way to add IdleTimer to class components. More about the higher order component here.

Provider#

If you need to access the IdleTimer API in nested children, you can opt to use the IdleTimerProvider rather than drilling props through your App's hierarchy. More about the Provider and related exports here.

Activate Method#

The reset method will now reset IdleTimer to it's initial state only. It will no longer call onActive if the user was idle. This functionality has been replaced by the activate() method. You can call activate() to manually activate a user, resetting the timer and calling onActive if the user was idle.

Made withby Randy Lebeau