Fabric/iOS: strokeDasharray cannot be cleared on a recycled view (missing else in RNSVGFabricConversions.h)

Author: vexxatuCreated Jul 29, 2026Updated Jul 29, 2026
LabelsMissing infoRepro provided

Description

On Fabric/iOS, strokeDasharray is the one stroke property that cannot be cleared on a recycled view. When React reuses a native RNSVGCircle (or any renderable) for an element whose new props omit strokeDasharray, the view keeps the dash pattern from its previous occupant, and a solid stroke renders dashed.

The cause is a missing else in apple/Utils/RNSVGFabricConversions.h:

objc
id strokeDasharray = RNSVGConvertFollyDynamicToId(renderableProps.strokeDasharray);
if (strokeDasharray != nil) {
  renderableNode.strokeDasharray = [RCTConvert RNSVGLengthArray:strokeDasharray];
}
// no else — a previously-set dasharray is never reset

Most neighbouring stroke props in the same function assign unconditionally, so they self-clear on recycle: stroke, strokeOpacity, strokeLinecap, strokeLinejoin, strokeDashoffset, strokeMiterlimit. Only strokeWidth and strokeDasharray are wrapped in a nil-check with no else branch, and strokeWidth is far less visible because it is rarely toggled between "set" and "unset" on the same element.

There is no JS-side value that clears it either

This is the part that makes it a genuine dead end rather than an inconvenience, and it is worth stating explicitly because "none" is the obvious workaround to reach for:

  1. Omitting the propsrc/lib/extract/extractStroke.ts guards with if (strokeDasharray != null), so the key is never written onto the props object and nothing reaches native.
  2. strokeDasharray="none" — the guard passes, but the branch resolves to null:
    typescript
    const strokeDash = !strokeDasharray || strokeDasharray === 'none' ? null : extractLengthList(strokeDasharray);
    That null arrives as folly::dynamic::NULLT, and RNSVGConvertFollyDynamicToId returns nil for NULLT — so the native if (… != nil) is false and the assignment is skipped again. "none" is indistinguishable from omitting the prop.
  3. strokeDasharray="0" — reaches native (extractLengthList('0')[0], odd-length, concatenated to [0, 0]), so it does overwrite the stale value. Whether CoreGraphics renders an all-zero pattern as solid is a separate question; the point is it is the only input that reaches the setter at all.

So from JS there is no clean way to express "this element has no dash."

Steps to reproduce

Render two Circles swapped by a ternary, where only one sets strokeDasharray, with no key on either branch (a keyless fragment is unwrapped, so index 0 matches by type and the native view is recycled):

javascript
const [dashed, setDashed] = useState(true);

<Svg width={120} height={120}>
  {dashed ? (
    <Circle cx={60} cy={60} r={50} stroke="#888" strokeWidth={6} fill="none" strokeDasharray="1 6" />
  ) : (
    <Circle cx={60} cy={60} r={50} stroke="#2a7" strokeWidth={6} fill="none" />
  )}
</Svg>
<Button title="toggle" onPress={() => setDashed(d => !d)} />

Toggle once.

Expected: the second circle draws a solid stroke. Actual: it draws with 1 6 dashing inherited from the first. Adding distinct keys to the two branches fixes it, which confirms recycling is the trigger.

Impact

This is easy to ship without noticing. In our case a dashed "unknown" ring and a solid "logged" ring shared a position in a week strip; the defect existed for weeks while the dash pattern was dense (2 3.5, which read as solid), and only became visible when the pattern was changed to a sparser 1 6. The rendering was wrong the whole time.

Suggested fix

Give the two guarded properties an else that resets to the default, matching what the unconditional assignments in the same function already do:

objc
if (strokeDasharray != nil) {
  renderableNode.strokeDasharray = [RCTConvert RNSVGLengthArray:strokeDasharray];
} else {
  renderableNode.strokeDasharray = nil;
}

RNSVGRenderable's draw path already guards on strokeDasharray.count, so a nil array is handled correctly downstream.

I'm happy to open a PR if this framing looks right.

Version info

package version
react-native-svg 15.15.4
react-native 0.85.3
architecture Fabric / New Architecture
platform iOS

Source: software-mansion/react-native-svg