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.

Additions in v5.7.0#

A legacy bundle is now included to support older browsers.

import { useIdleTimer } from 'react-idle-timer/legacy'

A new property has been added called disabled. This is designed to reduce friction for the common use case of starting and stopping the timer based on a boolean value. Below is an example using redux.

Old way:

import { useEffect } from 'react'
import { useSelector } from 'react-redux'
import { useIdleTimer } from 'react-idle-timer'
export const App = () => {
const loggedIn = useSelector(state => state.user !== undefined)
const { start, pause } = useIdleTimer({ startManually: true })
useEffect(() => {
if (loggedIn) {
start()
} else {
pause()
}
}, [ loggedIn ])
}

New way:

import { useSelector } from 'react-redux'
import { useIdleTimer } from 'react-idle-timer'
export const App = () => {
const loggedIn = useSelector(state => state.user !== undefined)
useIdleTimer({ disabled: !loggedIn })
}

By abstracting the complexity to IdleTimer's internals, your code becomes cleaner and more streamlined. When the timer is disabled, the control methods (start, reset, activate, pause, resume, and message) are also disabled. These methods now return a boolean value indicating whether the call was processed or ignored.

In addition to these two features, the 5.7.0 release integrates project maintenance and housekeeping tasks, which encompass the following:

  • Updating all dependencies.
  • Transitioning to Issue Form Templates.
  • Transitioning to WebStorm IDE.
  • Enhancing exports from package.json.
  • Improving the build chain.

Additions in v5.6.0#

All callback functions now pass the IIdleTimer interface as the second parameter. This will make it easier to interface with your timer during events. You won't have to rely on hoisting to access the API.

Here is an example using the new callback parameters to close a prompt on user activity:

const onAction = (event: Event, idleTimer: IIdleTimer) => {
if (idleTimer.isPrompted()) {
idleTimer.activate()
}
}
const onActive = (event: Event, idleTimer: IIdleTimer) => {
if (idleTimer.isPrompted()) {
setOpen(false)
}
}
const onPrompt = () => {
setOpen(true)
}
const onIdle = () => {
setOpen(false)
history.replace('/logout')
}
useIdleTimer({
timeout: 1000 * 60 * 5,
promptBeforeIdle: 1000 * 30,
onAction,
onActive,
onPrompt,
onIdle
})

Deprecations in v5.5.0#

The promptTimeout property has been deprecated in favor of the promptBeforeIdle. Both properties will continue to work until version 6.0.0, but it is encouraged to use the new promptBeforeIdle property. It is easier to reason about one timeout rather than two. The timeout property becomes the time until idle and the promptBeforeIdle property becomes how many milliseconds before idle to emit the onPrompt event. For example, if you want a 30 minute time to idle and a 30 second prompt:

const idleTimer = useIdleTimer({
timeout: 1000 * 60 * 30,
promptBeforeIdle: 1000 * 30
})

Migrating from v4#

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 use 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 your 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 tab's 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. 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 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 CPU cycles. This can cause IdleTimer to produce delayed and/or inaccurate results. To combat this, the timers interface has been exposed as a property. In addition, an implementation of Web Worker thread timers has been exported to keep timers 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 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, IdleTimer will handle it for you.

The new promptBeforeIdle property along with the onPrompt() event handler and the isPrompted() state getter is all you need to prompt your user before they go 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 promptBeforeIdle = 1000 * 30
// Modal open state
const [open, setOpen] = useState(false)
// Time before idle
const [remaining, setRemaining] = useState(0)
const onPrompt = () => {
// onPrompt will be called `promptBeforeIdle` milliseconds before `timeout`.
// In this case 29 minutes and 30 seconds or 30 seconds before idle.
// Here you can open your prompt.
// All events are disabled while the prompt is active.
// If the user wishes to stay active, call the `activate()` method.
// You can get the remaining prompt time with the `getRemainingTime()` method,
setOpen(true)
setRemaining(promptTimeout)
}
const onIdle = () => {
// onIdle will be called after the timeout is reached.
// In this case 30 minutes. Here you can close your prompt and
// perform whatever idle action you want such as logging 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 `activate()` 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,
promptBeforeIdle,
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 its 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, which will reset the timer and emit the onActive event if the user was idle or prompted.

Unified Event Handler#

The onPresenceChange event handler provides a way to define a single event handler to handle all presence state changes. This can be used in place of onActive, onIdle, and onPrompt to handle all state changes in one function. If you opt to use both the onPresenceChange generic handler and the specific handlers, make note that the generic handler will be called first.

// The presence type definition
type PresenceType = { type: 'idle' } | { type: 'active', prompted: boolean }
// Example onPresenceChange implementation
import type { PresenceType } from 'react-idle-timer'
const onPresenceChange = (presence: PresenceType) => {
const isIdle = presence.type === 'idle'
const isActive = presence.type === 'active' && !presence.prompted
const isPrompted = presence.type === 'active' && presence.prompted
}

Resettable Time Getters#

Added getIdleTime and getActiveTime to differentiate getTotalIdleTime and getTotalActiveTime methods. The new methods are reset by calling the reset method. The existing getTotal*Time methods will return the time since the host component was mounted and is not resettable. This addition adds clarity and control to the activity time getter methods.

Made withby Randy Lebeau