Skip to main content

Managing App State and Lifecycle

1. Using AppState API

Property Description Notes
AppState.currentState Current state of the app String value indicating app state (e.g., 'active', 'background')
AppState.addEventListener Subscribe to app state changes Event name: 'change'

Example: Listening to App State Changes

import { AppState } from 'react-native';\n\nconst handleAppStateChange = (nextAppState) => {\n  console.log('App state changed to:', nextAppState);\n};\n\nAppState.addEventListener('change', handleAppStateChange);\n\n// Clean up listener when component unmounts\nconst unsubscribe = AppState.addEventListener('change', handleAppStateChange);\nunsubscribe.remove();
Note: On iOS, the app state can be 'active', 'background', or 'inactive'. On Android, it can be 'active', 'background', or 'unknown'.

2. Handling App Background Events

Event Description Usage
appState Current state of the app (active, background, inactive) AppState.addEventListener('change', handleAppStateChange);

Example: Listening to App State Changes

import { AppState } from 'react-native';\n\nconst handleAppStateChange = (nextAppState) => {\n  if (nextAppState === 'background') {\n    console.log('App went to background');\n  } else if (nextAppState === 'active') {\n    console.log('App is active');\n  }\n};\n\nAppState.addEventListener('change', handleAppStateChange);
Note: The appState can be one of: 'active', 'background', or 'inactive'. It's useful for managing resources like network requests or timers.
Warning: On some platforms, background and inactive states might be indistinguishable.

3. Implementing Component Lifecycle

Method Description Arguments Usage
constructor() Initializes the component's state and binds methods props Called before component mounts
static getDerivedStateFromProps() Updates state based on props changes nextProps, prevState Called before rendering, both on initial mount and updates
shouldComponentUpdate() Controls if component should re-render nextProps, nextState Return false to prevent re-rendering
getSnapshotBeforeUpdate() Captures DOM info before update prevProps, prevState Return value is passed to componentDidUpdate
componentDidUpdate() Runs after component updates prevProps, prevState, snapshot Useful for side effects after updates
componentWillUnmount() Cleans up before component unmounts None Useful for clearing timers or subscriptions

Example: Component Lifecycle Methods

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    return { count: nextProps.value };
  }

  shouldComponentUpdate(nextProps, nextState) {
    return nextState.count !== this.state.count;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    return { scrollTop: this.scrollContainer.scrollTop };
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    this.scrollContainer.scrollTop = snapshot.scrollTop;
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }
}
Note: In React 16.3+, use getDerivedStateFromProps instead of componentWillReceiveProps, which is deprecated.
Warning: Avoid side effects in shouldComponentUpdate as it can impact performance.

4. Using useEffect for Side Effects

The useEffect hook is a fundamental tool for managing side effects in React Native components. It allows you to perform operations like data fetching, subscriptions, or DOM manipulations after a component renders.

Parameter Description Notes
effect A function that contains the side effect logic to run. Runs after every render by default
dependencies An array of values that the effect depends on. If omitted, effect runs after every render. If empty array [], effect runs only once.

Example: Fetching Data

import React, { useState, useEffect } from 'react';
import { View, Text } from 'react-native';

const DataComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Fetch error:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <Text>Loading...</Text>;
  return <View><Text>Data: {JSON.stringify(data)}</Text></View>;
&#125;;

export default DataComponent;

Example: Setting Up and Cleaning Up Subscriptions

import React, &#123; useEffect, useState &#125; from 'react';
import { View, Text } from 'react-native';

const SubscriptionComponent = () => &#123;
  const [time, setTime] = useState(new Date());

  useEffect(() => &#123;
    const intervalId = setInterval(() => &#123;
      setTime(new Date());
    &#125;, 1000);

    // Cleanup function
    return () => clearInterval(intervalId);
  &#125;, []);

  return <View><Text>Current time: {time.toLocaleTimeString()}</Text></View>;
&#125;;

export default SubscriptionComponent;
Note: The effect function runs after the component renders, but before the browser paints. If you need to perform a synchronous operation (like DOM measurements), use useLayoutEffect instead.

Key Points

  • useEffect is used to handle side effects in functional components.
  • It runs after every render by default, but can be controlled with a dependency array.
  • Return a cleanup function to handle subscriptions or timers when the component unmounts.

5. Managing Component State

State Type Description Usage Notes
useState Basic state hook for functional components const [state, setState] = useState(initialValue); Re-renders component on state change
useReducer Advanced state management using reducer function const [state, dispatch] = useReducer(reducer, initialState); Better for complex state logic or when next state depends on previous
useContext Access global state without prop drilling const value = useContext(MyContext); Use with React.createContext

Example: useState basic usage

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onPress={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Example: useReducer for complex state

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onPress={() => dispatch({ type: 'increment' })}>+</button>
      <button onPress={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}
Note: useState is suitable for simple local component state, while useReducer is better for managing more complex state logic or when the next state depends on the previous one.

6. Implementing State Persistence

Example: Using AsyncStorage for Local Storage

import AsyncStorage from '@react-native-async-storage/async-storage';

const saveData = async (key, value) => {
  try {
    await AsyncStorage.setItem(key, JSON.stringify(value));
  } catch (error) {
    console.error('Error saving data:', error);
  }
};

const loadData = async (key) => {
  try {
    const value = await AsyncStorage.getItem(key);
    return value ? JSON.parse(value) : null;
  } catch (error) {
    console.error('Error loading data:', error);
    return null;
  }
};

// Usage
saveData('userPreferences', { theme: 'dark', notifications: true });
loadData('userPreferences').then(data => console.log(data));
Method Description Notes
AsyncStorage.setItem(key, value) Stores data with a key Value must be stringified
AsyncStorage.getItem(key) Retrieves data by key Returns null if not found
AsyncStorage.removeItem(key) Deletes data by key No return value
AsyncStorage.clear() Removes all stored data Use with caution
Note: AsyncStorage is not suitable for sensitive data like passwords or tokens. Use secure storage solutions like react-native-keychain for such cases.

Example: Using Redux Persist with React Native

import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  stateReconciler: autoMergeLevel2
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);

// In your app component
import { PersistGate } from 'redux-persist/integration/react';

<PersistGate loading={null} persistor={persistor}>
  <App />
</PersistGate>

Comparison: AsyncStorage vs Redux Persist

  • AsyncStorage: Low-level storage, manual implementation
  • Redux Persist: Higher-level integration with Redux state
  • AsyncStorage: No automatic rehydration
  • Redux Persist: Automatic state rehydration on app start

7. Handling App Resume

Event Description Usage
appState 'active', 'background', 'inactive' State of the app

Example: Listening to App State Changes

import { AppState } from 'react-native';\n\nconst handleAppStateChange = (nextAppState) => {\n  if (nextAppState === 'active') {\n    console.log('App has come to the foreground');\n  } else if (nextAppState === 'background') {\n    console.log('App has gone to the background');\n  }\n};\n\nAppState.addEventListener('change', handleAppStateChange);\n\n// Don't forget to remove the listener when component unmounts\nconst unsubscribe = AppState.addEventListener('change', handleAppStateChange);\nunsubscribe.remove();
Note: On iOS, the active state is not always sent when resuming from background.

Key Points

  • AppState provides a way to detect when the app state changes.
  • Use AppState.addEventListener to listen for state changes.
  • Handle the 'active' state to perform actions when the app resumes.

8. Using useReducer for Complex State

Example: Basic useReducer Setup

import React, { useReducer } from 'react';

const initialState = {
  count: 0,
  name: 'React Native'
};

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'SET_NAME':
      return { ...state, name: action.name };
    default:
      throw new Error();
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <View>
      <Text>Count: {state.count}</Text>
      <Text>Name: {state.name}</Text>
      <Button title="Increment" onPress={() => dispatch({ type: 'INCREMENT' })} />
      <Button title="Set Name" onPress={() => dispatch({ type: 'SET_NAME', name: 'New Name' })} />
    </View>
  );
}
Property Description Usage
useReducer Hook to manage complex state logic const [state, dispatch] = useReducer(reducer, initialState);
reducer Function that takes state and action, returns new state function reducer(state, action) { ... }
initialState Initial value for the state { count: 0, name: 'React Native' }
dispatch Function to send actions to the reducer dispatch({ type: 'INCREMENT' })
Note: useReducer is preferred over useState for complex state logic or when the next state depends on the previous one.

9. Implementing Global State Patterns

Example: Using Redux Toolkit for Global State

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;
Pattern Description Use Case Pros Cons
Redux Toolkit Centralized state management with predictable updates Complex app state, multiple components sharing data Dev tools, middleware support, easy to test Steep learning curve, boilerplate for simple cases
Context API Lightweight state sharing across component tree Simple global state, no need for complex middleware No external dependencies, built-in to React Not scalable for large apps, performance issues with frequent updates
Zustand Minimalist state management with flexible API Small to medium apps, need for simplicity and performance Easy setup, minimal boilerplate, supports middleware Less mature ecosystem than Redux
Note: Choose Redux Toolkit for large-scale apps with complex state logic and need for debugging tools.
Warning: Avoid overusing global state; it can make components tightly coupled and harder to test.

State Management Summary

  • Redux Toolkit is best for large, complex applications with many state transitions.
  • Context API is suitable for simple state sharing without additional dependencies.
  • Zustand offers a balance between simplicity and power for mid-sized projects.

10. Managing Async State Updates

Method Description Usage
useState Hook to manage local component state const [state, setState] = useState(initialValue);
useEffect Hook to handle side effects and async operations useEffect(() => { /* async logic */ }, [dependencies]);
useReducer Alternative to useState for complex state logic const [state, dispatch] = useReducer(reducer, initialState);

Example: Async State Update with useEffect

import React, { useState, useEffect } from 'react';

function AsyncComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('/api/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error("Fetch error:", error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <div>Loading...</div>;
  return <div>Data: {JSON.stringify(data)}</div>;
}
Note: Always handle async operations inside useEffect to prevent memory leaks and ensure proper cleanup.
Warning: Avoid calling state setters directly inside useEffect without proper dependency arrays.

Was this article helpful?