Working with App State and Event Listeners in React Native
How to manage app state with event listeners and React hooks

This article introduces the concept of App State, and explores common scenarios where App State is used in React Native — that is accessed via the AppState object supplied by the framework.
We’ll cover how event listeners can access a component’s most up to date state via refs and the useRef hook, that can in turn prepare your app for the background or foreground. AppState changes will also be demonstrated in conjunction with React context and a Timer class, that will measure how long an app is in the background or foreground.
Although the subjects talked about can be applied to both iOS and Android, the demos in this article have been tested solely on iOS. These useful tools and concepts that will undoubtedly be useful as you develop React Native apps. Let’s get started with AppState.
How to Use App State
AppState is a simple API supplied by the react-native framework, so is most likely readily available in your React Native projects now. In it’s most basic usage, we can simply refer to the current App State using its currentState property, that will either be active, inactive or background:
// get current app state from `AppState`import React, { useState } from 'react'
import { AppState } from 'react-native'const App = (props) => {
const [appState, setAppState] = useState(AppState.currentState);
...
}In the above example, the App component will store the current state of the app as it is rendered — which will almost certainly be active.
This alone is not too useful — the app needs to know when this state changes, which in turn needs to be reflected in the above useState hook. To tackle this, event listeners can be attached to AppState, that gives the component the opportunity to update with the underlying value:
// `AppState` event listeners within `useEffect`
const handleAppStateChange = (state: any) => {
console.log(state);
}useEffect(() => {
AppState.addEventListener('change', handleAppStateChange);
return (() => {
AppState.removeEventListener('change', handleAppStateChange);
})
}, []);A useEffect hook has been introduced here with an empty dependency array, ensuring the event listeners will only mount upon the component’s initial render. useEffect’s return function is executed when the component unmounts, giving the component an opportunity to remove the event listener.
Let’s make this slightly more intelligent by updating the AppState within handleAppStateChange, and use another useEffect hook to console.log that value upon the subsequent re-render:
// listening to `AppState` changes
const [appState, setAppState] = useState(AppState.currentState);const handleAppStateChange = (state: any) => {
setAppState(state);
}useEffect(() => {
AppState.addEventListener('change', handleAppStateChange);
return (() => {
AppState.removeEventListener('change', handleAppStateChange);
})
}, []);useEffect(() => {
console.log(appState);
});With this simple setup, we already have the means to handle updates to the App State from within a component. However, there are indeed some limitations to our event listeners in this setup, as they will only ever be aware of the component state at the initial render. We will explore why this is the case further down the article.
But when do these “changes” actually occur? Let’s examine this next in order to understand exactly when these events are firing.
When AppState changes happen
The three values of AppState (active, inactive and background) are toggled between two key events:
- Minimising and opening the app, to and from the Home screen. Upon doing so, the app switches between
activeandbackground, with a temporary state ofinactiveas the app is being minimised. - Entering the app switcher from the app itself. If this is done from within the app, the app state will persistently change to
inactiveuntil the user leaves app switcher.
To demonstrate this, I have copied the example code from above into the Dashboard screen of an app I personally develop. Notice the changes between App State as I change from foreground to background, and as I enter the app switcher:

What you may have noticed is that we always have a period of inactive when minimising the app to the device’s Home screen, before changing again to background. In addition, this inactive state is only triggered from within the app. If you attempt to go into the app switcher while on the device’s Home screen, the app will remain in the background state, until it is opened to the foreground again.
Notice how some apps blur their screens in app switcher?
It is in the temporary inactive period that you can make some interesting changes to the app, such as overriding the current screen with a placeholder screen, in the event that the current screen contains confidential information — such as a banking app or FinTech app.
This can be handled simply be rendering a different screen if AppState.currentState is inactive. If you want this behaviour globally, you could wrap your entire app around a component, say, an <AppStateManager> component, that will re-render the app from the top level when the state changes to inactive:
// render `inactive` screen via top-level `AppStateManager` component
export const AppStateManager = (props: any) => { const [appState, setAppState] = useState(AppState.currentState); const handleAppStateChange = (state: any) => {
setAppState(state);
} useEffect(() => {
AppState.addEventListener('change', handleAppStateChange);
return (() => {
AppState.removeEventListener('change', handleAppStateChange);
})
}, []); return (
{appState === 'inactive'
? <View><Text>Inactive Screen!</Text><View>
: <>{props.children}</>
}
)
}This adds a certain level of security within React Native apps — an arguably compulsory feature for more sensitive applications.
With a high level understanding of AppState, let’s now examine how to overcome the limitation of event listeners, that only read component state from the render the event listeners are initialised. This can be solved with Refs.
Using Refs with Event Listeners to Access True State Values
As mentioned above, event listeners will only be aware of the state of the component at the initial render. Because the event listeners are not updated upon subsequent re-renders (when state changes), they will not be aware of those changes taking place.
To see this problem in action, we can increment a counter that will exist in useState, and log that counter within an event listener as it is being incremented. As the event listener is not aware of state updates after it is initialised, the counter will always log zero.
The following snippet sets this demo up with an event listener added to react-navigation’s didFocus event.
For testing purposes, React Navigation’s didFocus and didBlur events are really useful for testing component logic, that are triggered as screens are visited and left.
This event is initialised as the screen in question is visited for the first time — it is the state at this point that will be logged:
const [counter, setCounter] = useState(0);// update state every 2 secondssetInterval(() => {
setCounter(counter + 1);
}, 2000);// console.log `counter` within event listener every 2 secondsuseEffect(() => {
this.focusListener = props.navigation.addListener('didFocus', async () => {
setInterval(() => {
// this will always be 0
console.log(counter);
}, 2000);
});
return (() => {
this.focusListener.remove();
})
}, []);Concretely, the event listener will not have access to updated state values. This is an inherent issue to React’s relationship to event listeners in general, and is not just related to AppState.
To overcome this, we can use the useRef hook, as well as React.createRef(), to access real-time state values (from the most recent update), from useState or from DOM elements.
Firstly visiting useRef, we can give event listeners a true state value by making a couple of small changes from the above code:
- Creating a reference to
counterwithuseRef, and use that inside event listeners instead of usingcounterdirectly. - Defining a custom
setCountermethod that will update the ref’scurrentvalue as well as thecounterstate value. To do this, we can change the name of useState’ssetCounterto_setCounter, and use this inside our customsetCountermethod.
That may be hard to visualise — here is the updated counter example with useRef integrated:






