javascriptreactjsreact-native

Issue with conditional back-press handling


I'm trying to display a confirmation modal on back-press using BackHandler in a React Native application. I have a local state of type string (postText) that holds some text. If the length of postText is greater than 0, I want to show a confirmation modal and prevent the default back action.

However, the condition (postText?.length > 0) is not working as expected. The back button always performs the default action, even when the condition is true. Here's my code:

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

const App = () => {
  const [postText, setPostText] = useState<string>('');

  useEffect(() => {
    const onBack = () => {
      if (postText?.length > 0) {
        // Show confirmation modal
        console.log('Showing confirmation modal');
        return true; // Prevent default back action
      }
      return false; // Allow default back action
    };

    const subscription = BackHandler.addEventListener(
      'hardwareBackPress',
      onBack
    );

    return () => {
      subscription.remove();
    };
  }, [postText]); // Dependency array includes postText

  return null; // Simplified for brevity
};

export default App;

Solution

  • Possible that the useEffect persists the initial value of postText and doesn't update on changes if the dependency array is not properly defined or managed. This happens because the closure of the useEffect function captures the state of postText at the time of the initial render and doesn't reflect subsequent updates unless explicitly instructed through dependencies. Use a stable ref for postText to prevent dependency re-triggering issues.

    import React, { useState, useEffect, useRef } from 'react';
    import { BackHandler, Alert } from 'react-native';
    
    const App = () => {
      const [postText, setPostText] = useState<string>('');
      const postTextRef = useRef(postText);
    
      useEffect(() => {
        postTextRef.current = postText; // Keep the ref updated
      }, [postText]);
    
      useEffect(() => {
        const onBack = () => {
          if (postTextRef.current.length > 0) {
            return true; // Prevent default back action
          }
          return false; // Allow default back action
        };
    
        BackHandler.addEventListener('hardwareBackPress', onBack);
    
        return () => {
          BackHandler.removeEventListener('hardwareBackPress', onBack);
        };
      }, []);
    
      return null; // Simplified for brevity
    };
    
    export default App;