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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions