Course outline

React Native / Inclusive native systems and release evidence / Lesson 14

Animation, profiling, and list cost

Measure the expensive phase and preserve animation lifetime instead of applying universal memoization or list settings.

About 4 min readContent reviewed
On this page

Lesson goals

  • Define the interaction and measured host
  • Own an animation and stop its superseded work
  • Fixed-row offsets are a contract, not an estimate

Define the interaction and measured host

A performance investigation needs a concrete workload, build mode, runtime, target hardware, dataset, and observed metric. Debug overhead and a desktop JavaScript model are not a release-performance baseline for a phone. Distinguish delayed JavaScript work, native layout or drawing, image decoding, and memory retention before choosing an intervention. A smooth native-driven opacity animation does not prove a long synchronous JavaScript calculation is harmless. Likewise, reducing rerenders does not automatically repair expensive native effects. Preserve correctness, localization, and accessibility while comparing the same representative interaction before and after a focused change.

Own an animation and stop its superseded work

The first module keeps one Animated.Value under a stable ref and starts a new opacity target when visible changes. Cleanup stops the animation created by that Effect. The native driver is selected for a supported opacity animation, not as a universal driver for arbitrary layout properties. Duration180 is an illustrative design choice. Opacity zero does not itself define hit testing, focus, or accessibility hiding, so an interactive hidden subtree needs additional policy. The example contains only text and does not claim those interactions are solved. No animation runtime or frame timing is executed here.

jsx Illustrative React Native module; requires the declared compatible application host.
import {useEffect, useRef} from 'react';
import {Animated, Text} from 'react-native';
export default function FadeLabel({visible}) {
  const opacity = useRef(new Animated.Value(visible ? 1 : 0)).current;
  useEffect(() => {
    const animation = Animated.timing(opacity, {
      toValue: visible ? 1 : 0, duration: 180, useNativeDriver: true,
    });
    animation.start();
    return () => animation.stop();
  }, [visible, opacity]);
  return <Animated.View style={{opacity}}><Text>Workshop status</Text></Animated.View>;
}
Expected output
Changing visible requests opacity1 or0 and stops the previous Effect’s animation.

The fragment uses a supported opacity target. It does not assert frame pacing, native compilation, or hidden-input behavior.

Fixed-row offsets are a contract, not an estimate

The second program calculates an offset for a genuinely fixed-size row plus separator. It can inform getItemLayout only when the actual layout preserves those dimensions. Dynamic text height, different separators, headers, and layout direction can invalidate a guessed formula. Include every relevant extent rather than copying a number from a short English screenshot. The pure result verifies arithmetic only and provides no scrolling benchmark. A production list with variable-height accessible rows may require normal measurement instead of forcing fixed geometry that clips information merely to make offsets convenient.

javascript Standalone JavaScript model with synthetic local inputs.
function fixedRowLayout(index, rowExtent, separatorExtent) {
  if (!Number.isSafeInteger(index) || index < 0 || rowExtent <= 0 || separatorExtent < 0) {
    throw new Error('INVALID_LAYOUT');
  }
  return {length: rowExtent, offset: index * (rowExtent + separatorExtent), index};
}
console.log(JSON.stringify(fixedRowLayout(2, 48, 2)));
console.log(JSON.stringify(fixedRowLayout(0, 48, 2)));
Expected output
{"length":48,"offset":100,"index":2}
{"length":48,"offset":0,"index":0}

The model includes two separators before row2. It assumes finite numeric extents supplied by a validated fixed-layout contract.

Optimize measured work and retained resources

Memoization can avoid a repeated calculation only when dependencies are correct and the retained value is worth its memory. A stale callback closure can preserve wrong state even if it reduces allocations. FlatList window and batch settings trade responsiveness, blank regions, and memory; no single configuration is best for every device and row. Large decoded images can dominate memory independently of JavaScript object counts. Listeners, timers, tasks, and native references also need clear lifetime owners. Measure repeated open/close cycles to distinguish an intentional cache plateau from continued retention rather than changing every optimization knob at once.

Report improvements only from comparable evidence

Capture the slow interaction, change one plausible cause, repeat the same workload, and compare the relevant trace and memory or frame observations. Retain tests for row identity, cancellation, long Arabic text, and reduced-motion behavior when changing implementation. A faster tiny Node function is not evidence that a native list or animation improved. This course performs no native profiler run, image decoding benchmark, renderer timing, or device load. Its standalone arithmetic models establish exact values only; target-specific performance claims require actual compatible host measurements and documented limitations.

Common errors and fixes

Native driver is enabled for an unsupported layout animation

Why it happens
A supported opacity example was generalized to every property.
How to fix it
Check the selected animation API’s supported properties and host.
Prevent it next time
Keep driver assumptions explicit in examples.

getItemLayout scrolls to the wrong offset

Why it happens
The formula ignored actual row or separator dimensions.
How to fix it
Use a genuinely fixed contract or measured layout.
Prevent it next time
Test long scaled text and final rows.

Memoization preserves obsolete values

Why it happens
Dependencies or closure ownership were incomplete.
How to fix it
Repair the value contract before optimizing reuse.
Prevent it next time
Change every relevant input in a regression check.

Check your understanding

1. Why does opacity0 not establish that a panel is safely hidden?

Show worked solution

Visual transparency is separate from hit testing, focus, semantics, and draft lifetime. Decide those policies for an interactive subtree and verify transition as well as final states. A fade should not leave invisible controls active or discard work unexpectedly.

2. What evidence is needed before claiming a larger FlatList window is better?

Show worked solution

Measure the same representative scrolling interaction on intended targets, including blank regions, responsiveness, and memory. A larger window can improve one metric while worsening another. Preserve row correctness and accessibility and report the actual tradeoff instead of a universal setting.

Key takeaways

  • Define the interaction and measured host
  • Report improvements only from comparable evidence

Official references