Handling Key Events in React Native macOS: The Native Approach

After struggling with unreliable keyboard event handling in React Native macOS, I discovered the solution: handle key events at the native macOS level instead of trying to make JavaScript event handling work reliably.

The Problem

Standard React Native keyboard handling approaches often fail on macOS:

  • onKeyDown props don’t capture events consistently
  • document.addEventListener doesn’t exist in React Native
  • Focus management interferes with text editing
  • Chirping sounds when events aren’t properly consumed
  • Events get lost when focus changes between UI elements

The Solution: Native + React Native Bridge

The key insight is to handle keyboard events at the native macOS level using Objective-C, then bridge them to React Native when needed.

Step 1: Native Keyboard Handling (AppDelegate.mm)

Add native keyboard monitoring to your AppDelegate.mm:

#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTBridge.h>

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
  self.moduleName = @"YourAppName";
  self.initialProps = @{};
  [super applicationDidFinishLaunching:notification];
  
  // Set up native keyboard handling
  [self setupNativeKeyboardHandling];
}

- (void)setupNativeKeyboardHandling
{
  // Add local monitor for key events (equivalent to SwiftUI's onKeyPress)
  [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^NSEvent * _Nullable(NSEvent * _Nonnull event) {
    NSLog(@"🔑 NATIVE: Key event - keyCode: %d, characters: %@", (int)event.keyCode, event.characters);
    
    // Handle Escape key (keyCode 53)
    if (event.keyCode == 53) {
      NSLog(@"🔑 NATIVE: Escape key detected, sending to React Native");
      [self sendEscapeKeyToReactNative];
      
      // Return nil to consume the event (equivalent to SwiftUI's .handled)
      return nil;
    }
    
    // Return the event for other keys to continue normal processing
    return event;
  }];
  
  NSLog(@"🔑 NATIVE: Native keyboard handling setup complete");
}

- (void)sendEscapeKeyToReactNative
{
  // Send event to React Native via DeviceEventEmitter
  [[NSNotificationCenter defaultCenter] postNotificationName:@"NativeEscapeKeyPressed" object:nil];
  
  if (self.bridge) {
    [self.bridge.eventDispatcher sendAppEventWithName:@"NativeEscapeKeyPressed" body:@{}];
  }
}

@end

Step 2: React Native Event Listener (App.tsx)

Listen for native events in your React Native app:

import React from 'react';
import { Platform, View, DeviceEventEmitter } from 'react-native';

const GlobalKeyboardWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const { isVisible, closePanel } = useTaskDetailPanel();

  // Listen for native keyboard events from AppDelegate
  React.useEffect(() => {
    if (Platform.OS !== 'macos') return;

    console.log('🔑 NATIVE: Setting up native keyboard event listener');

    // Listen for native escape key events
    const subscription = DeviceEventEmitter.addListener('NativeEscapeKeyPressed', () => {
      console.log('🔑 NATIVE: Escape key received from native AppDelegate');
      if (isVisible) {
        console.log('🔑 NATIVE: Closing TaskDetailPanel via native event');
        closePanel();
      }
    });

    return () => {
      subscription.remove();
      console.log('🔑 NATIVE: Native keyboard event listener removed');
    };
  }, [isVisible, closePanel]);

  return (
    <View style={{ flex: 1 }}>
      {children}
    </View>
  );
};

// Wrap your app with the keyboard wrapper
function App() {
  return (
    <GlobalKeyboardWrapper>
      {/* Your app content */}
    </GlobalKeyboardWrapper>
  );
}

Key Benefits

  1. Reliable Event Capture: Native macOS event monitoring captures ALL key events
  2. No Chirping: Events are consumed at the native level (return nil)
  3. No Focus Issues: Doesn’t interfere with text editing or UI focus
  4. Performance: Native event handling is faster than JavaScript
  5. Extensible: Easy to add more key combinations

Key Codes Reference

Common macOS key codes for event.keyCode:

  • Escape: 53
  • Enter: 36
  • Space: 49
  • Arrow Up: 126
  • Arrow Down: 125
  • Arrow Left: 123
  • Arrow Right: 124

Why This Works

  1. Native Level: NSEvent addLocalMonitorForEventsMatchingMask captures events before they reach React Native
  2. Event Consumption: Returning nil consumes the event (like SwiftUI’s .handled)
  3. Selective Bridging: Only send events to React Native when needed
  4. Clean Separation: Native handles the mechanics, React Native handles the logic

This approach finally gave me reliable global keyboard shortcuts in React Native macOS with zero interference and zero chirping sounds! 🎉


Building cross-platform apps with React Native macOS. Sometimes you need to go native to get it right. 🚀

Robot Design Hub Launches

I’ve been working on this search engine site for robot designs for a couple months. It’s now deployed and I will soon launch it on ProductHunt. I used Coolify and Linode for this Python Flask app.

https://robots.greenrobot.com

I would love to know your comments on my design and site.

I am still seeking remote dev work

Hello, I am still seeking employment. I am an expert developer in many different languages and platforms. If you know anyone looking for a developer, let me know.

My resume is available at this link:
https://raw.githack.com/andytriboletti/publicfiles/main/resume/triboletti_andy_resume-latest.pdf

I created 4 new sites and filed 2 bug reports

•I created a react site on longevity:

https://longevity.greenrobot.com

•I created a php mysql new job search engine site specializing in AI and ML jobs – AI Careers:

https://aicareers.greenrobot.com

I created a node, express, firebase hosted firestore app for developers to get ready to launch their app:

https://launchday.greenrobot.com

I created a php sqlite mental health lawyer directory site:

https://mentalhealthlawyers.greenrobot.com

•I filed a bug report for recast-navigation-js:

https://github.com/isaac-mason/recast-navigation-js/issues/468

•I filed a bug report for instanced-mesh:

https://github.com/agargaro/instanced-mesh/issues/128

So annoyed with the speed of my wordpress blogs on dreamhost, I started a new open source project

I’ve tried to make my WordPress Dreamhost blogs faster, but I want to migrate everything over to Linode with new blogging software I created that uses SQLite and is easier to set up than WordPress. It’s still a work in progress:

https://github.com/greenrobotllc/greenblog

A personal post from Andy

blog.andytriboletti.com/2025/01/13/coincidences-2/

I assert the gov is spying on me under a branch of the Secret Service. Trump goes along with almost anything SS suggests. Why wouldn’t they just hire me instead of hacking my Pixel 6 phone with Pegasus? These days, just monitoring, I think. I think because I inadvertently predict the future, with the post linked being some PG-rated examples.

Hey Facebook, Mark Zuckerberg, thanks for suing the NSO group for hacking!

How do I get an acknowledgement that my phone was hacked, Pixel 6, and other phones I tried during that time, 2020-2022? And a list of the hacked texts sent.

Please, Please, Please. I want it so bad. They were horrific and threatening texts sent from my phone, which I didn’t write or send.

https://www.washingtonpost.com/technology/2025/05/06/nso-pegasus-whatsapp-damages/

I got $250 from a Google settlement. I wish there was a settlement for people who’ve had Pegasus trojan send texts that weren’t written by me, sent cashapp to a person not authorized by me, and even more annoying stuff. Like interrupting a call to my mom with audio she only heard. Weird death threat involving Thursdays from a voicemail transcription with the voice not saying the death stuff. Sending a bumble message not by me to a bizz connection. Rewriting text sms history. Sending tweets not in my name. Offensive and terrible Facebook posts not written by me. Once on a friend’s page. Rewriting at least 1 instagram comment on someone else’s page. Android keyboard grammar checker messing with posts as I wrote them, perfectly spelled words replaced. While I was sleeping sometimes with this stuff. My Mom will vouch she got a disturbing text while I was asleep. With my Pixel 6 a few years ago. Since Pixel 7 and on, no issues.

I heard Pegasus costs 500K per user!

Do you think people employed by the government/secret service are forced to go to mental institutions to evaluate/pick on people? I assert they do. Can we do anything about it?

Need help with this

I have an Android Google technical question if you have a minute sometime.

I clicked on a link to chat.com on my android phone and it brings up a “Rocks Player” app instead of chatgpt. I want to remove that app. As far as I know I never installed it. I searched for apps called Rocks player and no results found. My Android pixel 6 phone was hacked, with what I think was pegasus, sending texts I didn’t write, doing all sorts of bad stuff. I haven’t had a problem this year at all with my pixel 8. As well as doing that, I noticed when I connected my pixel 6 to my computer, it installed this app by itself. I think it’s malware. It got transferred to my phone when I upgraded to the new phone.

God, it makes me so mad I got hacked and never received an apology. I forgot about Rocks player app until now when I clicked on that link. I would love to sue. Whatsapp sued nso group, makers of pegasus.

I have submitted BBB complaints, contacted Google support multiple times. I want to sue.

I added cartoonify.greenrobot.com to greenrobot.com/apps; 3dwebgames.com updated.

I added Cartoonify to greenrobot.com/apps page. I worked on this cartoonify.greenrobot.com site before the OpenAI Studio Ghibli craze but never updated this site with the link. It’s running local AI models on my GPU server connected with Redis Queue. It’s free and ad supported.

I also updated 3dwebgames.com: Added a loading screen, reduced loading time, added ability for keyboard keys on desktop and virtual joystick on mobile to move the camera around, and other fixes. I made a lot of the virtual store site configurable via config files, so I can work on adding more stores to a virtual 3d mall. Like a TechShack with top tech products (inspired by RadioShack retail stores that no longer exist), a Pharmacy, a Job Fair conference, etc.