Skip to content

Commit ec4bbe3

Browse files
perf(mobile): cache initial SkPath in usePathTransition
`usePathTransition` was calling `Skia.Path.MakeFromSVGString` on every render to compute `initialSkiaPath`, but the result is only ever consumed as the initial value of the four `useSharedValue` calls below it. Since `useSharedValue` ignores its argument on every render after the first, every later parse was discarded. Switch to a `useState` lazy initializer so the parse runs exactly once per hook instance. In an Android prod CPU trace (1:03 capture), this hot path accounted for ~64% of all `MakeFromSVGString` self-time (~350ms over the trace) across `AnimatedPath` and `DottedArea` chart renders. `useState` is used rather than `useMemo` because `useMemo` is documented as a perf hint that React may discard, and any realistic dependency array would either fail the exhaustive-deps lint rule (`[]`) or re-parse on every `currentPath` change while the shared-value initializers continue to ignore the new result.
1 parent fb3cf07 commit ec4bbe3

1 file changed

Lines changed: 16 additions & 3 deletions

File tree

packages/mobile/src/visualizations/chart/utils/transition.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef } from 'react';
1+
import { useEffect, useMemo, useRef, useState } from 'react';
22
import {
33
type ExtrapolationType,
44
type SharedValue,
@@ -265,8 +265,21 @@ export const usePathTransition = ({
265265
const interpolatorRef = useRef<((t: number) => string) | null>(null);
266266
const progress = useSharedValue(0);
267267

268-
const initialSkiaPath =
269-
Skia.Path.MakeFromSVGString(initialPath ?? currentPath) ?? Skia.Path.Make();
268+
// Parse the SVG path exactly once per hook instance via `useState`'s lazy
269+
// initializer. `initialSkiaPath` is only consumed as the initial value for
270+
// the `useSharedValue` calls below, and `useSharedValue` ignores its
271+
// argument on every render after the first. Recomputing the parse on every
272+
// render is therefore pure waste — in an Android prod CPU trace this hot
273+
// path accounted for ~64% of all `Skia.Path.MakeFromSVGString` self-time
274+
// (~350ms over 1:03) across `AnimatedPath` / `DottedArea` chart renders.
275+
//
276+
// `useState` (not `useMemo`) is intentional: `useMemo` is documented as a
277+
// performance hint that React may discard, and any realistic dependency
278+
// array would either lint-fail (`[]`) or re-parse on every `currentPath`
279+
// change while the shared-value initializers still ignore the new result.
280+
const [initialSkiaPath] = useState(
281+
() => Skia.Path.MakeFromSVGString(initialPath ?? currentPath) ?? Skia.Path.Make(),
282+
);
270283
const normalizedStartShared = useSharedValue(initialSkiaPath);
271284
const normalizedEndShared = useSharedValue(initialSkiaPath);
272285
const fallbackPathShared = useSharedValue(initialSkiaPath);

0 commit comments

Comments
 (0)