React Native for TV
This package provides a fork of React Native specifically adapted for developing applications on TV platforms, including Apple TV (tvOS) and Android TV. It extends the core React Native framework with TV-specific navigation primitives, focus management, and input handling necessary for an optimal 10-foot user experience. The current stable version, `0.85.0-0`, aligns closely with mainstream React Native releases (e.g., React Native 0.85.x) but maintains its own maintenance cycle to integrate TV-specific features and fixes. Development appears active, with frequent updates tracking React Native's primary release train, evidenced by recent `0.85.0-0` release candidates and the final `0.85.0-0` release following `0.84.x` and `0.83.x`. Its key differentiators lie in its specialized focus engine, robust remote control input integration, and UI component adaptations for large screen environments, making it the primary choice for building native TV applications with React.
Common errors
-
Error: Command 'run-ios' unrecognized. Did you mean run-android, run-ios, run-windows?
cause Attempting to run `react-native run-ios` for a tvOS project without specifying the correct scheme or destination for Apple TV.fixUse `npx react-native run-ios --scheme <YourApp>-tvOS --destination 'platform=tvOS Simulator,name=Apple TV'` or similar. Ensure your Xcode project has a tvOS scheme configured and that `react-native-tvos` is properly integrated. -
Invariant Violation: View config not found for name ReactNativeARTGroup. Make sure to start with 'RCT' or 'RK'
cause Missing native module linkage or an issue with the native build configuration, often after an upgrade or adding a new native library. This specific error often relates to legacy drawing libraries.fixFor iOS/tvOS, navigate to the `ios` directory and run `pod install`. For Android, run `gradlew clean` and rebuild the project. Confirm that `react-native-tvos` native modules are correctly linked and that autolinking is working. -
TypeError: Cannot read property 'View' of undefined. (or similar for other core components)
cause This typically occurs when `react-native-tvos` is not correctly aliased to `react-native` in `package.json`, causing the bundler to try and load core `react-native` which lacks TV-specific exports or expects a different native setup.fixAdd or update your `package.json` to include `"resolutions": { "react-native": "npm:react-native-tvos@latest" }` (for Yarn) or `"overrides": { "react-native": "npm:react-native-tvos@latest" }` (for npm 8+) to alias the package properly. Then, reinstall dependencies (`yarn install` or `npm install`). -
Cannot find name 'React'. Did you mean 'react'?
cause Incorrect `React` import in a TypeScript file or missing `@types/react`.fixEnsure you have `import React from 'react';` at the top of your `.tsx` or `.jsx` files and that `@types/react` is installed as a dev dependency (`npm install --save-dev @types/react`). -
Focus does not return to the initial element after re-applying `hasTVPreferredFocus: true`
cause A known issue in certain versions where `setNativeProps({ hasTVPreferredFocus: true })` does not reliably re-apply focus after an element has lost and regained focus once.fixThis is a regression in specific versions. Consider upgrading to a patched version if available. Workarounds might involve manually managing focus state or using `TVFocusGuideView` to explicitly guide focus.
Warnings
- breaking A regression was introduced in `disabled=true` behavior on Android TV (issue #1058), potentially causing UI elements with this prop to not behave as expected or still receive focus/interaction.
- breaking Node.js engine requirements have become stricter with recent releases. Using an unsupported Node.js version (e.g., below v20.19.4 for current versions) can lead to build failures or unexpected runtime errors during development.
- gotcha Focus management is critical for TV applications. Overriding default focus behavior or not handling focusable elements correctly can lead to a poor user experience, especially with remote control navigation.
- gotcha While based on React Native, `react-native-tvos` includes specific adaptations. Direct usage of some web-oriented components or third-party libraries without TV-specific considerations might lead to unexpected behavior or require manual adjustments for focus and input.
- deprecated Older versions of React Native for TV might use deprecated APIs or have different behaviors regarding event handlers (e.g., older `TVEventHandler` patterns). Ensure you are using the latest recommended patterns for remote input.
- gotcha Upgrading `react-native-tvos` often involves a corresponding upgrade of the core React Native dependency, which can introduce breaking changes from the upstream project. Manual linking or specific Xcode/Android Studio steps might be required.
Install
-
npm install react-native-tvos -
yarn add react-native-tvos -
pnpm add react-native-tvos
Imports
- View, Text, StyleSheet
const { View, Text, StyleSheet } = require('react-native-tvos');import { View, Text, StyleSheet } from 'react-native-tvos'; - TouchableOpacity
import { TouchableOpacity } from 'react-native-tvos'; - TVEventHandler
import { TVEventHandler } from 'react-native-tvos'; - TVFocusGuideView
import { TVFocusGuideView } from 'react-native-tvos';
Quickstart
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, TVEventHandler } from 'react-native-tvos';
const App = () => {
const [focusedItem, setFocusedItem] = useState('none');
useEffect(() => {
const tvEventHandler = new TVEventHandler();
tvEventHandler.enable(this, (cmp, evt) => {
if (evt && evt.eventKind === 'select') {
console.log(`Item selected: ${focusedItem}`);
// Handle selection logic based on focusedItem
}
});
return () => {
tvEventHandler.disable();
};
}, [focusedItem]);
return (
<View style={styles.container}>
<Text style={styles.header}>Welcome to React Native TV!</Text>
<TouchableOpacity
onFocus={() => setFocusedItem('Button 1')}
onBlur={() => setFocusedItem('none')}
style={[styles.button, focusedItem === 'Button 1' && styles.buttonFocused]}
activeOpacity={0.8}
testID="button1"
>
<Text style={styles.buttonText}>Button 1</Text>
</TouchableOpacity>
<TouchableOpacity
onFocus={() => setFocusedItem('Button 2')}
onBlur={() => setFocusedItem('none')}
style={[styles.button, focusedItem === 'Button 2' && styles.buttonFocused]}
activeOpacity={0.8}
testID="button2"
>
<Text style={styles.buttonText}>Button 2</Text>
</TouchableOpacity>
<Text style={styles.footer}>Currently focused: {focusedItem}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#222',
},
header: {
fontSize: 48,
color: 'white',
marginBottom: 40,
},
button: {
backgroundColor: '#4CAF50',
paddingVertical: 20,
paddingHorizontal: 50,
borderRadius: 10,
marginVertical: 15,
width: 400,
alignItems: 'center',
},
buttonFocused: {
borderColor: 'yellow',
borderWidth: 5,
},
buttonText: {
color: 'white',
fontSize: 32,
fontWeight: 'bold',
},
footer: {
fontSize: 28,
color: '#AAA',
marginTop: 40,
},
});
export default App;