Skip to content

underlineColorAndroid silently ignored on initial render with Fabric (New Architecture) #57921

Description

@HADeveloper

Description

On Android with the New Architecture (Fabric), underlineColorAndroid set as a prop on TextInput is silently dropped on the initial render. The dark material underline remains visible. The prop applies correctly after any subsequent re-render (state change, hot reload).

React Native version: 0.83.x (first version where this is unavoidable — Expo SDK 55 made Fabric mandatory)
Platform: Android only
Architecture: New Architecture (Fabric) only — did not reproduce on Paper/Bridge

Steps to reproduce

<TextInput underlineColorAndroid="transparent" />

On initial mount: the native Android underline is visible.
After any re-render: the underline disappears correctly.

Expected behavior

underlineColorAndroid is applied on initial mount, consistent with the old architecture behavior.

Actual behavior

The prop is silently dropped on initial mount. The ReactEditText shows its default material underline.

Root cause

ReactTextInputManager.kt, setUnderlineColor (unchanged between 0.82 and 0.83):

@ReactProp(name = "underlineColorAndroid", customType = "Color")
public fun setUnderlineColor(view: ReactEditText, underlineColor: Int?) {
    val background = view.background

    if (background == null) {
      return  // ← prop silently dropped if background not yet populated
    }
    // ...
}

In Fabric, createViewInstance creates the ReactEditText and immediately applies all initial props synchronously. If the view's background drawable is null at that moment (no material theme on the ThemedReactContext, or editTextBackground attr not resolved yet), setUnderlineColor hits the early return and the prop is lost.

A secondary issue: if BackgroundStyleApplicator.setBackgroundColor() runs first, it wraps the original background in a CompositeBackgroundDrawable (LayerDrawable). Calling setColorFilter on a LayerDrawable does not propagate to child drawables in all Android versions, so the underline drawable inside the wrapper is unaffected.

On the update path (re-render), the background is already populated and the color filter applies successfully — which is why subsequent renders work.

Workaround

Call setNativeProps({ underlineColorAndroid }) in a useEffect after mount, which goes through the update path:

const ref = useRef<TextInput>(null)

useEffect(() => {
  ref.current?.setNativeProps({ underlineColorAndroid: 'transparent' })
}, [])

<TextInput ref={ref} underlineColorAndroid="transparent" />

Suggested fix

In setUnderlineColor, rather than silently returning when background == null, ensure the background drawable is initialized before applying the color filter — either by calling ensureCompositeBackgroundDrawable(view) or by initializing a default background from the EditText theme attribute.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: Author FeedbackNeeds: ReproThis issue could be improved with a clear list of steps to reproduce the issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions