From af40f98f2380861a95f8219557384b497c0dee95 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Tue, 21 Jun 2016 14:57:53 -0700 Subject: [PATCH] [fix] AppState event handler registration Fix #151 --- docs/apis/AppState.md | 3 +- examples/index.js | 5 ++- package.json | 2 +- src/apis/AppState/__tests__/index-test.js | 28 +++++++++++++- src/apis/AppState/index.js | 43 ++++++++++++++++----- src/components/ScrollView/ScrollViewBase.js | 2 +- 6 files changed, 67 insertions(+), 16 deletions(-) diff --git a/docs/apis/AppState.md b/docs/apis/AppState.md index 5a4e4d10..06223db6 100644 --- a/docs/apis/AppState.md +++ b/docs/apis/AppState.md @@ -37,7 +37,6 @@ class Example extends React.Component { constructor(props) { super(props) this.state = { currentAppState: AppState.currentState } - this._handleAppStateChange = this._handleAppStateChange.bind(this) } componentDidMount() { @@ -48,7 +47,7 @@ class Example extends React.Component { AppState.removeEventListener('change', this._handleAppStateChange); } - _handleAppStateChange(currentAppState) { + _handleAppStateChange = (currentAppState) => { this.setState({ currentAppState }); } diff --git a/examples/index.js b/examples/index.js index fd7322fa..5acea6e1 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,7 +1,10 @@ import { AppRegistry } from 'react-native' +import App from './components/App' import Game2048 from './2048/Game2048' import TicTacToeApp from './TicTacToe/TicTacToe' -AppRegistry.runApplication('TicTacToeApp', { +AppRegistry.registerComponent('App', () => App) + +AppRegistry.runApplication('App', { rootTag: document.getElementById('react-root') }) diff --git a/package.json b/package.json index 04e657f9..f98bfb37 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "babel-runtime": "^6.9.2", "fbjs": "^0.8.1", "inline-style-prefix-all": "1.0.5", - "lodash.debounce": "^4.0.6", + "lodash": "^4.13.1", "react-textarea-autosize": "^4.0.2", "react-timer-mixin": "^0.13.3" }, diff --git a/src/apis/AppState/__tests__/index-test.js b/src/apis/AppState/__tests__/index-test.js index 7870afbf..8c0aa28c 100644 --- a/src/apis/AppState/__tests__/index-test.js +++ b/src/apis/AppState/__tests__/index-test.js @@ -1,5 +1,31 @@ /* eslint-env mocha */ +import AppState from '..' +import assert from 'assert' + suite('apis/AppState', () => { - test.skip('NO TEST COVERAGE', () => {}) + const handler = () => {} + + teardown(() => { + try { AppState.removeEventListener('change', handler) } catch (e) {} + }) + + suite('addEventListener', () => { + test('throws if the provided "eventType" is not supported', () => { + assert.throws(() => AppState.addEventListener('foo', handler)) + assert.doesNotThrow(() => AppState.addEventListener('change', handler)) + }) + }) + + suite('removeEventListener', () => { + test('throws if the handler is not registered', () => { + assert.throws(() => AppState.removeEventListener('change', handler)) + }) + + test('throws if the provided "eventType" is not supported', () => { + AppState.addEventListener('change', handler) + assert.throws(() => AppState.removeEventListener('foo', handler)) + assert.doesNotThrow(() => AppState.removeEventListener('change', handler)) + }) + }) }) diff --git a/src/apis/AppState/index.js b/src/apis/AppState/index.js index 14b34ac5..e71d930c 100644 --- a/src/apis/AppState/index.js +++ b/src/apis/AppState/index.js @@ -1,30 +1,53 @@ +import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment' +import findIndex from 'lodash/findIndex' import invariant from 'fbjs/lib/invariant' -const listeners = {} -const eventTypes = [ 'change' ] +const EVENT_TYPES = [ 'change' ] +const VISIBILITY_CHANGE_EVENT = 'visibilitychange' + +const AppStates = { + BACKGROUND: 'background', + ACTIVE: 'active' +} + +const listeners = [] class AppState { + static isSupported = ExecutionEnvironment.canUseDOM && document.visibilityState + static get currentState() { + if (!AppState.isSupported) { + return AppState.ACTIVE + } + switch (document.visibilityState) { case 'hidden': case 'prerender': case 'unloaded': - return 'background' + return AppStates.BACKGROUND default: - return 'active' + return AppStates.ACTIVE } } static addEventListener(type: string, handler: Function) { - listeners[handler] = () => handler(AppState.currentState) - invariant(eventTypes.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type) - document.addEventListener('visibilitychange', listeners[handler], false) + if (AppState.isSupported) { + invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type) + const callback = () => handler(AppState.currentState) + listeners.push([ handler, callback ]) + document.addEventListener(VISIBILITY_CHANGE_EVENT, callback, false) + } } static removeEventListener(type: string, handler: Function) { - invariant(eventTypes.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type) - document.removeEventListener('visibilitychange', listeners[handler], false) - delete listeners[handler] + if (AppState.isSupported) { + invariant(EVENT_TYPES.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type) + const listenerIndex = findIndex(listeners, (pair) => pair[0] === handler) + invariant(listenerIndex !== -1, 'Trying to remove AppState listener for unregistered handler') + const callback = listeners[listenerIndex][1] + document.removeEventListener(VISIBILITY_CHANGE_EVENT, callback, false) + listeners.splice(listenerIndex, 1) + } } } diff --git a/src/components/ScrollView/ScrollViewBase.js b/src/components/ScrollView/ScrollViewBase.js index 46a976ff..1a446569 100644 --- a/src/components/ScrollView/ScrollViewBase.js +++ b/src/components/ScrollView/ScrollViewBase.js @@ -6,7 +6,7 @@ * @flow */ -import debounce from 'lodash.debounce' +import debounce from 'lodash/debounce' import React, { Component, PropTypes } from 'react' import View from '../View'