Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use withSafeTimeout in NUX tips #7544

Merged
merged 2 commits into from
Jun 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions components/higher-order/with-safe-timeout/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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.
`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 `setTimeout` will never be called if the original component instance ceases to exist in the meantime.

## Usage

Expand All @@ -11,11 +11,11 @@ withSafeTimeout
*/
import { withSafeTimeout } from '@wordpress/components';

function MyEffectfulComponent( { setSafeTimeout } ) {
function MyEffectfulComponent( { setTimeout } ) {
return (
<TextField
onBlur={ () => {
setSafeTimeout( delayedAction, 0 );
setTimeout( delayedAction, 0 );
} }
/>
);
Expand Down
85 changes: 44 additions & 41 deletions components/higher-order/with-safe-timeout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { without } from 'lodash';
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { createHigherOrderComponent, Component } from '@wordpress/element';

/**
* Browser dependencies
Expand All @@ -17,47 +17,50 @@ 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
* @param {Component} OriginalComponent Component requiring setTimeout
*
* @return {Component} Wrapped component.
* @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 }
/>
);
}
};
}
const withSafeTimeout = createHigherOrderComponent(
( 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 }
/>
);
}
};
},
'withSafeTimeout'
);

export default withSafeTimeout;
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ exports[`DefaultBlockAppender should append a default block when input focused 1
<WithSelect(WithDispatch(Inserter))
position="top right"
>
<WithSelect(WithDispatch(DotTip))
<WithSafeTimeout(WithSelect(WithDispatch(DotTip)))
id="core/editor.inserter"
>
Welcome to the wonderful world of blocks! Click the “+” (“Add block”) button to add a new block. There are blocks available for all kind of content: you can insert text, headings, images, lists, and lots more!
</WithSelect(WithDispatch(DotTip))>
</WithSafeTimeout(WithSelect(WithDispatch(DotTip)))>
</WithSelect(WithDispatch(Inserter))>
</div>
`;
Expand All @@ -69,11 +69,11 @@ exports[`DefaultBlockAppender should match snapshot 1`] = `
<WithSelect(WithDispatch(Inserter))
position="top right"
>
<WithSelect(WithDispatch(DotTip))
<WithSafeTimeout(WithSelect(WithDispatch(DotTip)))
id="core/editor.inserter"
>
Welcome to the wonderful world of blocks! Click the “+” (“Add block”) button to add a new block. There are blocks available for all kind of content: you can insert text, headings, images, lists, and lots more!
</WithSelect(WithDispatch(DotTip))>
</WithSafeTimeout(WithSelect(WithDispatch(DotTip)))>
</WithSelect(WithDispatch(Inserter))>
</div>
`;
Expand All @@ -99,11 +99,11 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = `
<WithSelect(WithDispatch(Inserter))
position="top right"
>
<WithSelect(WithDispatch(DotTip))
<WithSafeTimeout(WithSelect(WithDispatch(DotTip)))
id="core/editor.inserter"
>
Welcome to the wonderful world of blocks! Click the “+” (“Add block”) button to add a new block. There are blocks available for all kind of content: you can insert text, headings, images, lists, and lots more!
</WithSelect(WithDispatch(DotTip))>
</WithSafeTimeout(WithSelect(WithDispatch(DotTip)))>
</WithSelect(WithDispatch(Inserter))>
</div>
`;
16 changes: 7 additions & 9 deletions nux/components/dot-tip/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
/**
* External dependencies
*/
import { defer } from 'lodash';

/**
* WordPress dependencies
*/
import { Component, createRef, compose } from '@wordpress/element';
import { Popover, Button, IconButton } from '@wordpress/components';
import { Popover, Button, IconButton, withSafeTimeout } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { withSelect, withDispatch } from '@wordpress/data';

Expand All @@ -24,16 +19,18 @@ export class DotTip extends Component {
}

componentDidMount() {
if ( this.props.isVisible ) {
const { isVisible, setTimeout } = this.props;

if ( isVisible ) {
// Force the popover to recalculate its position on the next frame. This
// fixes the tip not appearing next to the inserter toggle on page load. This
// happens because the popover calculates its position before <PostTitle> is
// made visible, resulting in the position being too high on the page.
defer( () => {
setTimeout( () => {
const popover = this.popoverRef.current;
popover.refresh();
popover.focus();
} );
}, 0 );
}
}

Expand Down Expand Up @@ -74,6 +71,7 @@ export class DotTip extends Component {
}

export default compose(
withSafeTimeout,
withSelect( ( select, { id } ) => {
const { isTipVisible, getAssociatedGuide } = select( 'core/nux' );
const associatedGuide = getAssociatedGuide( id );
Expand Down
7 changes: 4 additions & 3 deletions nux/components/dot-tip/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import { shallow } from 'enzyme';
import { noop } from 'lodash';

/**
* Internal dependencies
Expand All @@ -20,7 +21,7 @@ describe( 'DotTip', () => {

it( 'should render correctly', () => {
const wrapper = shallow(
<DotTip isVisible>
<DotTip isVisible setTimeout={ noop }>
It looks like you’re writing a letter. Would you like help?
</DotTip>
);
Expand All @@ -30,7 +31,7 @@ describe( 'DotTip', () => {
it( 'should call onDismiss when the dismiss button is clicked', () => {
const onDismiss = jest.fn();
const wrapper = shallow(
<DotTip isVisible onDismiss={ onDismiss }>
<DotTip isVisible onDismiss={ onDismiss } setTimeout={ noop }>
It looks like you’re writing a letter. Would you like help?
</DotTip>
);
Expand All @@ -41,7 +42,7 @@ describe( 'DotTip', () => {
it( 'should call onDisable when the X button is clicked', () => {
const onDisable = jest.fn();
const wrapper = shallow(
<DotTip isVisible onDisable={ onDisable }>
<DotTip isVisible onDisable={ onDisable } setTimeout={ noop }>
It looks like you’re writing a letter. Would you like help?
</DotTip>
);
Expand Down