Skip to content

Commit

Permalink
Add withSafeTimeout higher-order component
Browse files Browse the repository at this point in the history
  • Loading branch information
mcsf committed Feb 9, 2018
1 parent 4be5974 commit a87280a
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 0 deletions.
25 changes: 25 additions & 0 deletions components/higher-order/with-safe-timeout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
withSafeTimeout
===============

`withSafeTimeout` is a React [higher-order component](https://facebook.github.io/react/docs/higher-order-components.html) which provides a special version of `window.setTimeout` which respects the original component's lifecycle. Simply put, a function set to be called in the future via `setSafeTimeout` will never be called if the original component instance ceases to exist in the meantime.

## Usage

```jsx
/**
* WordPress dependencies
*/
import { withSafeTimeout } from '@wordpress/components';

function MyEffectfulComponent( { setSafeTimeout } ) {
return (
<TextField
onBlur={ () => {
setSafeTimeout( delayedAction, 0 );
} }
/>
);
}

export default withSafeTimeout( MyEffectfulComponent );
```
63 changes: 63 additions & 0 deletions components/higher-order/with-safe-timeout/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* External dependencies
*/
import { without } from 'lodash';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';

/**
* Browser dependencies
*/
const { clearTimeout, setTimeout } = window;

/**
* A higher-order component used to provide and manage delayed function calls
* that ought to be bound to a component's lifecycle.
*
* @param {Component} OriginalComponent Component requiring setTimeout
*
* @return {Component} Wrapped component.
*/
function withSafeTimeout( OriginalComponent ) {
return class WrappedComponent extends Component {
constructor() {
super( ...arguments );
this.timeouts = [];
this.setTimeout = this.setTimeout.bind( this );
this.clearTimeout = this.clearTimeout.bind( this );
}

componentWillUnmount() {
this.timeouts.forEach( clearTimeout );
}

setTimeout( fn, delay ) {
const id = setTimeout( () => {
fn();
this.clearTimeout( id );
}, delay );
this.timeouts.push( id );
return id;
}

clearTimeout( id ) {
clearTimeout( id );
this.timeouts = without( this.timeouts, id );
}

render() {
return (
<OriginalComponent
{ ...this.props }
setTimeout={ this.setTimeout }
clearTimeout={ this.clearTimeout }
/>
);
}
};
}

export default withSafeTimeout;
1 change: 1 addition & 0 deletions components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ export { default as withFilters } from './higher-order/with-filters';
export { default as withFocusOutside } from './higher-order/with-focus-outside';
export { default as withFocusReturn } from './higher-order/with-focus-return';
export { default as withInstanceId } from './higher-order/with-instance-id';
export { default as withSafeTimeout } from './higher-order/with-safe-timeout';
export { default as withSpokenMessages } from './higher-order/with-spoken-messages';
export { default as withState } from './higher-order/with-state';

0 comments on commit a87280a

Please sign in to comment.