Skip to content

fix: preserve className styles on multi-config and non-style array targets#320

Open
Inv1x wants to merge 2 commits intonativewind:mainfrom
Inv1x:fix/multi-config-style-merging
Open

fix: preserve className styles on multi-config and non-style array targets#320
Inv1x wants to merge 2 commits intonativewind:mainfrom
Inv1x:fix/multi-config-style-merging

Conversation

@Inv1x
Copy link
Copy Markdown

@Inv1x Inv1x commented Apr 4, 2026

Summary

Fixes two related bugs where className-computed styles were silently destroyed:

  • Non-style array targets (e.g. ["contentContainerStyle"]): deepMergeConfig did not merge className + inline styles for length-1 array targets. Inline values simply overwrote the className-computed value instead of producing a style array (the behavior already implemented for the ["style"] path).
  • Multi-config components (e.g. ScrollView with both className→style and contentContainerClassName→contentContainerStyle): getStyledProps iterates over multiple configs, and each iteration produced a full props object via Object.assign. Later iterations overwrote earlier iterations' correctly-merged target props.

Reproduction

// Bug 1: contentContainerClassName styles destroyed by inline contentContainerStyle
<ScrollView
  contentContainerClassName="bg-green"
  contentContainerStyle={{ padding: 10 }}
/>
// Expected: contentContainerStyle = [{ backgroundColor: "#008000" }, { padding: 10 }]
// Actual:   contentContainerStyle = { padding: 10 }  ← className styles lost

// Bug 2: className styles destroyed on multi-config ScrollView
<ScrollView className="bg-red" style={{ paddingTop: 10 }} />
// Expected: style = [{ backgroundColor: "#f00" }, { paddingTop: 10 }]
// Actual:   style = { paddingTop: 10 }  ← className styles lost

Fix

  1. Add a finalKey merge block for length-1 array targets in deepMergeConfig, mirroring the existing string-target merge logic.
  2. Save each config iteration's computed target value in getStyledProps and restore them after the loop completes.

Test plan

  • Added tests for ScrollView with contentContainerClassName + contentContainerStyle (non-overlapping props produce array, undefined/null preserves className)
  • Added tests for multi-config ScrollView: className + style preserved when contentContainerClassName also present
  • Added tests for FlatList contentContainerClassName + contentContainerStyle
  • Added test for custom styled() with string target and style={undefined}
  • Added test for consumed className sources cleanup
  • All 1040 tests pass, typecheck and lint clean

…rgets

Two related bugs caused className-computed styles to be silently destroyed:

1. deepMergeConfig did not merge className + inline styles for length-1
   array targets like ["contentContainerStyle"]. Inline values simply
   overwrote the className-computed value instead of producing a style
   array (the behavior already implemented for the ["style"] path).

2. getStyledProps iterates over multiple configs (e.g. ScrollView has
   className→style and contentContainerClassName→contentContainerStyle).
   Each iteration produced a full props object via Object.assign, so
   later iterations overwrote earlier iterations' correctly-merged
   target props.

Fix (1) by adding a finalKey merge block for length-1 array targets in
deepMergeConfig, mirroring the existing string-target merge logic.

Fix (2) by saving each config iteration's computed target value and
restoring them after the loop completes.

Adds tests for both paths including ScrollView, FlatList, and custom
styled() components with className + inline style combinations.
Copilot AI review requested due to automatic review settings April 4, 2026 12:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes cases where className-derived native styles were being lost when merging with inline props, especially for non-style array targets and multi-config components.

Changes:

  • Update getStyledProps to preserve previously-merged target props across multiple config iterations.
  • Update deepMergeConfig to merge className + inline styles for length-1 array targets (e.g. ["contentContainerStyle"]).
  • Add/adjust tests covering ScrollView/FlatList and multi-config scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/native/styles/index.ts Adjusts merge logic for multi-config iteration and length-1 array targets to avoid losing computed styles.
src/tests/native/className-with-style.test.tsx Adds regression tests for non-style targets and multi-config components to ensure computed styles are preserved.

- Guard finalKey block with config.target.length === 1 so it only runs
  for length-1 array targets, not nested paths handled by recursion
- Explicitly write filtered rightValue in all code paths to prevent
  unfiltered VAR_SYMBOL objects from leaking into RN props
- Add defensive comment about leaf-key storage limitation in getStyledProps
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants