Skip to content

fix(native): map native styles onto new objects, leaving the computed props untouched - #505

Closed
YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/native-style-mapping-copy
Closed

YevheniiKotyrlo wants to merge 1 commit into
nativewind:mainfrom
YevheniiKotyrlo:fix/native-style-mapping-copy

Conversation

@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor

Problem

nativeStyleMapping deletes each mapped declaration from its source and writes the value into the props it is handed. Those props are the computed props cached for every render of the component, and getStyledProps hands them over once per config through adaptProps. For most components that is the cache itself, so a config sees an earlier config's mapping only because the cache was changed. For React Native's Image with object-fit, adaptImageProps returns a new object on each call, so a config that follows a mapping starts from a fresh copy and the mapped value is lost:

const TintedImage = styled(Image, {
  tintClassName: {
    target: "style",
    nativeStyleMapping: { color: "tintColor" },
  },
  className: "style",
});

// tintColor is undefined
<TintedImage source={source} className="object-contain" tintClassName="text-red-500" />;

Solution

  • nativeStyleMapping builds new objects along the paths it changes and leaves its input as it was, with withDeepPath and withoutKeys beside getDeepPath and setDeepPath in src/native/objects.ts.
  • getStyledProps adapts the computed props once per render, and each config maps the props the config before it produced.

Tests

  • src/__tests__/native/image-fit.test.tsx: on an Image with object-fit, a tint mapping followed by another config keeps tintColor on the first render and after a re-render, for a normal and an !important declaration. On main the normal case loses it on the first render.
  • src/__tests__/native/styled.test.ios.tsx: a mapping onto a nested target maps nothing before its class is set, then the same value on every render.
  • src/__tests__/native/objects.test.ts: withDeepPath and withoutKeys copy what they change and leave their input untouched.

Verification

Every ci.yml job passes on this head with #477's harness fix: lint, typecheck, the tests, both builds, the web export, nothing unstaged, commitlint, and the iOS dev build on macOS 27.0.1 with Xcode 27.0, where CI pins 26.6.

A render costs the same: 3000 re-renders of a mapped View take 61.3 µs each here against 62.3 µs on main, a plain View 16.7 µs against 16.3, and a mapped Image with object-fit 27.0 µs against 29.0, each the median of five runs on Windows with Node 26, within each side's spread.

No existing issue tracks this. I searched for nativeStyleMapping, tintColor, object-fit and nativeStyleToProp.

Merge order

… props untouched

nativeStyleMapping drained the computed props in place. Those props are
cached and shared by every render, and an Image with object-fit maps a copy
whose nested objects are still the cached ones, so a mapped value was lost
when another config followed it. Each config now maps the props the config
before it produced, and the mapping copies only the objects it changes.
@YevheniiKotyrlo

Copy link
Copy Markdown
Contributor Author

Folded into #435, which needs this change to stay correct.

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.

1 participant