diff --git a/packages/docs/src/pages/docs/apis/app-state.md b/packages/docs/src/pages/docs/apis/app-state.md index 7b624f04..cd67cadb 100644 --- a/packages/docs/src/pages/docs/apis/app-state.md +++ b/packages/docs/src/pages/docs/apis/app-state.md @@ -36,7 +36,7 @@ Returns the current state of the app. ### Static methods -{% call macro.prop('addEventListener', '(type: ?string, listener: (boolean) => void) => void') %} +{% call macro.prop('addEventListener', '(type: ?string, listener: (boolean) => void) => ?EmitterSubscription') %} Add a listener to `AppState` changes. Listen to the `"change"` event type. The handler is called with the app state value. {% endcall %} diff --git a/packages/examples/pages/app-state/index.js b/packages/examples/pages/app-state/index.js index 8185a496..bcac89e2 100644 --- a/packages/examples/pages/app-state/index.js +++ b/packages/examples/pages/app-state/index.js @@ -18,9 +18,9 @@ export default function AppStatePage() { })); }; - AppState.addEventListener('change', handleChange); + const subscription = AppState.addEventListener('change', handleChange); return () => { - AppState.removeEventListener('change', handleChange); + subscription.remove(); }; }, []); diff --git a/packages/react-native-web/src/exports/AppState/__tests__/index-test.js b/packages/react-native-web/src/exports/AppState/__tests__/index-test.js index a05860ff..5bd76bc7 100644 --- a/packages/react-native-web/src/exports/AppState/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/AppState/__tests__/index-test.js @@ -5,22 +5,22 @@ import AppState from '..'; describe('apis/AppState', () => { const handler = () => {}; - afterEach(() => { - try { - AppState.removeEventListener('change', handler); - } catch (e) {} - }); - describe('addEventListener', () => { test('throws if the provided "eventType" is not supported', () => { expect(() => AppState.addEventListener('foo', handler)).toThrow(); - expect(() => AppState.addEventListener('change', handler)).not.toThrow(); + expect(() => AppState.addEventListener('change', handler).remove()).not.toThrow(); }); }); describe('removeEventListener', () => { - test('throws if the handler is not registered', () => { - expect(() => AppState.removeEventListener('change', handler)).toThrow(); + beforeEach(() => { + // removeEventListener logs a deprecation warning, ignore + jest.spyOn(console, 'error'); + console.error.mockImplementation(() => {}); + }); + + afterEach(() => { + console.error.mockRestore(); }); test('throws if the provided "eventType" is not supported', () => { diff --git a/packages/react-native-web/src/exports/AppState/index.js b/packages/react-native-web/src/exports/AppState/index.js index 72211864..dcf756c4 100644 --- a/packages/react-native-web/src/exports/AppState/index.js +++ b/packages/react-native-web/src/exports/AppState/index.js @@ -9,8 +9,8 @@ */ import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; -import findIndex from 'array-find-index'; import invariant from 'fbjs/lib/invariant'; +import EventEmitter from '../../vendor/react-native/emitter/_EventEmitter'; // Android 4.4 browser const isPrefixed = @@ -25,7 +25,7 @@ const AppStates = { ACTIVE: 'active' }; -const listeners = []; +let changeEmitter = null; export default class AppState { static isAvailable = canUseDOM && document[VISIBILITY_STATE_PROPERTY]; @@ -53,9 +53,19 @@ export default class AppState { type ); if (type === 'change') { - const callback = () => handler(AppState.currentState); - listeners.push([handler, callback]); - document.addEventListener(VISIBILITY_CHANGE_EVENT, callback, false); + if (!changeEmitter) { + changeEmitter = new EventEmitter(); + document.addEventListener( + VISIBILITY_CHANGE_EVENT, + () => { + if (changeEmitter) { + changeEmitter.emit('change', AppState.currentState); + } + }, + false + ); + } + return changeEmitter.addListener(type, handler); } } } @@ -67,15 +77,8 @@ export default class AppState { 'Trying to remove listener for unknown event: "%s"', type ); - if (type === 'change') { - 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); + if (type === 'change' && changeEmitter) { + changeEmitter.removeListener(handler); } } }