Skip to content

Nested Text Android Spans recreated on every render #50731

@fabOnReact

Description

@fabOnReact

Description

Updating any property of a Nested Text, triggers a full update of all the properties.
The consequence is that multiple spans are added to the TextView to display the same duplicate property, on every re-render. The redundant update process can lead to performance issues or unexpected behavior due to the unnecessary creation of spans.

  • I opened this issue to ask opinion from the React Native Team.
  • I would publish a series of Pull Request to improve React Native Nested Text.

I found out about this issue while testing feature flags Enable prop diffing for View #45551 and Calculate Android mounting instructions based on updates accumulated in rawProps #48303.

Steps to reproduce

I reproduced the issue in latests react-native main branch (fabOnReact@e57bcd3).

  1. Open the file packages/rn-tester/js/RNTesterAppShared.js
  2. Copy paste the example below
  3. Run the RNTester App on Android
  4. Quickly type a lot of text in the TextInput and the TextInput typing will become slow
CLICK TO OPEN TESTS EXAMPLE

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

export default function PartialSpanInput() {
  const [prevText, setPrevText] = useState('');
  const [newText, setNewText] = useState('');

  const onChangeText = nativeText => {
    const newTextWithRandomStyle = (
      <Text>
        {prevText}
        <Text style={getRandomStyle()}>
          {nativeText[nativeText.length - 1]}
        </Text>
      </Text>
    );
    setNewText(newTextWithRandomStyle);
  };

  useEffect(() => {
    setPrevText(newText);
  }, [newText]);

  const RANDOM_COLORS = ['red', 'green', 'blue', 'orange', 'purple'];
  const getRandomColor = () => {
    return RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)];
  };

  const getRandomStyle = () => {
    return {
      color: getRandomColor(),
      fontSize: Math.floor(Math.random() * 20) + 10,
      fontWeight: Math.random() > 0.5 ? 'bold' : 'normal',
    };
  };

  return (
    <View style={styles.container}>
      <TextInput style={styles.input} multiline onChangeText={onChangeText}>
        {prevText}
        {newText}
      </TextInput>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {flex: 1, padding: 20, backgroundColor: '#f0f0f0'},
  input: {
    flex: 1,
    borderColor: '#ccc',
    borderWidth: 1,
    padding: 10,
  },
});

React Native Version

latest

Affected Platforms

Runtime - Android

Output of npx @react-native-community/cli info

info Fetching system and libraries information...
System:
  OS: macOS 15.3.2
  CPU: (10) arm64 Apple M1 Pro
  Memory: 156.53 MB / 16.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.14.0
    path: /nix/store/2yk2lr8y92mmkr4kcwwdlnr4hscvnps2-nodejs-22.14.0/bin/node
  Yarn:
    version: 1.22.19
    path: ~/.nix-profile/bin/yarn
  npm:
    version: 10.9.2
    path: /nix/store/2yk2lr8y92mmkr4kcwwdlnr4hscvnps2-nodejs-22.14.0/bin/npm
  Watchman:
    version: 2024.03.11.00
    path: /Users/fabriziobertoglio/.nix-profile/bin/watchman
Managers:
  CocoaPods:
    version: 1.15.2
    path: /Users/fabriziobertoglio/.nix-profile/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.2
      - iOS 18.2
      - macOS 15.2
      - tvOS 18.2
      - visionOS 2.2
      - watchOS 11.2
  Android SDK:
    API Levels:
      - "28"
      - "30"
      - "31"
      - "32"
      - "33"
      - "34"
      - "35"
    Build Tools:
      - 28.0.2
      - 28.0.3
      - 29.0.0
      - 30.0.2
      - 30.0.3
      - 31.0.0
      - 32.0.0
      - 32.1.0
      - 33.0.0
      - 33.0.1
      - 34.0.0
      - 35.0.0
    System Images:
      - android-22 | ARM 64 v8a
      - android-28 | Google ARM64-V8a Play ARM 64 v8a
      - android-30 | China version of Wear OS 3 ARM 64 v8a
      - android-30 | Wear OS 3 ARM 64 v8a
      - android-31 | ARM 64 v8a
      - android-31 | Google APIs ARM 64 v8a
      - android-31 | Google Play ARM 64 v8a
      - android-33 | Wear OS 4 ARM 64 v8a
      - android-33 | Google APIs ARM 64 v8a
      - android-33 | Google Play ARM 64 v8a
      - android-35 | Google Play ARM 64 v8a
      - android-Tiramisu | Google TV ARM 64 v8a
      - android-Tiramisu | Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2024.2 AI-242.23339.11.2421.12700392
  Xcode:
    version: 16.2/16C5032a
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.14
    path: /usr/bin/javac
  Ruby:
    version: 2.6.10
    path: /Users/fabriziobertoglio/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 19.0.0
    wanted: 19.0.0
  react-native: Not Found
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: Not found
  newArchEnabled: Not found
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Stacktrace or Logs

04-15 20:53:52.387 13424 13424 W unknown:SetSpanOperation: Text tree size exceeded the limit, styling may become unpredictable
04-15 20:53:52.387 13424 13424 W unknown:SetSpanOperation: Text tree size exceeded the limit, styling may become unpredictable
04-15 20:53:52.387 13424 13424 W unknown:SetSpanOperation: Text tree size exceeded the limit, styling may become unpredictable
04-15 20:53:52.388 13424 13424 W unknown:SetSpanOperation: Text tree size exceeded the limit, styling may become unpredictable
04-15 20:53:52.388 13424 13424 W unknown:SetSpanOperation: Text tree size exceeded the limit, styling may become unpredictable
04-15 20:53:52.388 13424 13424 W unknown:TESTING : ReactTextInputManager updateExtraData com.facebook.react.views.text.ReactTextUpdate@52640cf
04-15 20:53:52.393   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.393   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.394   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.394   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.394   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.394   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.394   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.394   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.398 13424 13452 D EGL_emulation: app_time_stats: avg=1042.04ms min=811.05ms max=1273.02ms count=2
04-15 20:53:52.401 13424 13424 E unknown:TextLayoutManager: Set cached spannable for tag[4]: sdfsdfsd fsdafsdasadfdfsadfsdfsadfdfsdafasdfffffffffsdfsadffffffffffffffffffffffffffffffffffffffffffffffsdfsdfsd sdfsadfasdf
04-15 20:53:52.408 13424 13424 E unknown:TextLayoutManager: Set cached spannable for tag[4]: sdfsdfsd fsdafsdasadfdfsadfsdfsadfdfsdafasdfffffffffsdfsadffffffffffffffffffffffffffffffffffffffffffffffsdfsdfsd sdfsadfasdf
04-15 20:53:52.410 13424 13435 I OpenGLRenderer: Davey! duration=1266ms; Flags=0, FrameTimelineVsyncId=2754480, IntendedVsync=8044640266836, Vsync=8045706933460, InputEventId=56290598, HandleInputStart=8045723091835, AnimationStart=8045723092585, PerformTraversalsStart=8045892193794, DrawStart=8045893104710, FrameDeadline=8044673600168, FrameInterval=8045723030669, FrameStartTime=16666666, SyncQueued=8045894010877, SyncStart=8045894073794, IssueDrawCommandsStart=8045894113294, SwapBuffers=8045896157669, FrameCompleted=8045907210294, DequeueBufferDuration=2708, QueueBufferDuration=301209, GpuCompleted=8045907210294, SwapBuffersCompleted=8045898767544, DisplayPresentTime=8018659472906, CommandSubmissionCompleted=8045896157669,
04-15 20:53:52.634   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.634   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.634   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.635   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.636   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.636   589   681 I InputDispatcher: Dropped event because it is stale.
04-15 20:53:52.636   589   681 W InputDispatcher: Dispatching key to 861e36b com.facebook.react.uiapp/com.facebook.react.uiapp.RNTesterActivity even though there are other unprocessed events

Reproducer

fabOnReact@e57bcd3

Screenshots and Videos

The video was re-encoded with ffmpeg to decrease the size.

output.mp4

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions