diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
index 3bcff03e5f4be4..55d5cea6ae612d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
@@ -601,10 +601,13 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
     if (reactTextUpdate.getText().length() == 0) {
       setText(null);
     } else {
-      // When we update text, we trigger onChangeText code that will
-      // try to update state if the wrapper is available. Temporarily disable
-      // to prevent an infinite loop.
+      boolean shouldKeepCompositeSpan = length() == spannableStringBuilder.length();
+
       getText().replace(0, length(), spannableStringBuilder);
+
+      if (shouldKeepCompositeSpan) {
+        attachCompositeSpansToTextFrom(spannableStringBuilder);
+      }
     }
     mDisableTextDiffing = false;
 
@@ -619,10 +622,13 @@ public void maybeSetText(ReactTextUpdate reactTextUpdate) {
   }
 
   /**
-   * Remove and/or add {@link Spanned.SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
-   * as long as the text they cover is the same. All other spans will remain the same, since they
-   * will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
+   * Remove and/or add {@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE} spans, since they should only exist
+   * as long as the text they cover is the same unless they are {@link Spanned#SPAN_COMPOSING}.
+   * All other spans will remain the same, since they will adapt to the new text, hence why {@link SpannableStringBuilder#replace} never removes
    * them.
+   * Keep copy of {@link Spanned#SPAN_COMPOSING} Spans in {@param spannableStringBuilder}, because they are important for
+   * keyboard suggestions. Without keeping these Spans, suggestions default to be put after the current selection position,
+   * possibly resulting in letter duplication.
    */
   private void manageSpans(
       SpannableStringBuilder spannableStringBuilder, boolean skipAddSpansForMeasurements) {
@@ -632,6 +638,8 @@ private void manageSpans(
       int spanFlags = getText().getSpanFlags(span);
       boolean isExclusiveExclusive =
           (spanFlags & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
+      boolean isComposing =
+        (spanFlags & Spanned.SPAN_COMPOSING) == Spanned.SPAN_COMPOSING;
 
       // Remove all styling spans we might have previously set
       if (span instanceof ReactSpan) {
@@ -646,6 +654,12 @@ private void manageSpans(
       final int spanStart = getText().getSpanStart(span);
       final int spanEnd = getText().getSpanEnd(span);
 
+      // We keep a copy of Composing spans
+      if (isComposing) {
+        spannableStringBuilder.setSpan(span, spanStart, spanEnd, spanFlags);
+        continue;
+      }
+
       // Make sure the span is removed from existing text, otherwise the spans we set will be
       // ignored or it will cover text that has changed.
       getText().removeSpan(span);
@@ -683,6 +697,33 @@ private void stripAtributeEquivalentSpans(SpannableStringBuilder sb) {
     }
   }
 
+  /**
+   * Attaches the {@link Spanned#SPAN_COMPOSING} from {@param spannableStringBuilder} to {@link ReactEditText#getText}
+   *
+   * See {@link ReactEditText#manageSpans} for more details.
+   * Also https://github.com/facebook/react-native/issues/11068
+   */
+  private void attachCompositeSpansToTextFrom(SpannableStringBuilder spannableStringBuilder) {
+    Editable text = getText();
+    if (text == null) {
+      return;
+    }
+    Object[] spans = spannableStringBuilder.getSpans(0, length(), Object.class);
+    for (Object span : spans) {
+      int spanFlags = spannableStringBuilder.getSpanFlags(span);
+      boolean isComposing = (spanFlags & Spanned.SPAN_COMPOSING) == Spanned.SPAN_COMPOSING;
+
+      if (!isComposing) {
+        continue;
+      }
+
+      final int spanStart = spannableStringBuilder.getSpanStart(span);
+      final int spanEnd = spannableStringBuilder.getSpanEnd(span);
+
+      text.setSpan(span, spanStart, spanEnd, spanFlags);
+    }
+  }
+
   private void unstripAttributeEquivalentSpans(
       SpannableStringBuilder workingText, Spannable originalText) {
     // We must add spans back for Fabric to be able to measure, at lower precedence than any