as

Settings
Sign out
Notifications
Alexa
亚马逊应用商店
AWS
文档
Support
Contact Us
My Cases
新手入门
设计和开发
应用发布
参考
支持
感谢您的访问。此页面目前仅提供英语版本。我们正在开发中文版本。谢谢您的理解。

Improve App Performance with Concurrent Rendering

Concurrent rendering is a React 18 feature that lets the framework prepare multiple UI versions simultaneously. With concurrent rendering, React can interrupt, pause, abandon, or resume rendering work as needed. You can mark updates as urgent or non-urgent, allowing React to work on several state updates at once without blocking the main thread. React prioritizes urgent user interactions (typing, clicking, pressing) while handling less urgent updates (large screen updates, data fetches) in the background.

Concurrent rendering enables Vega apps to:

  • Maintain smooth playback and interactions: UI controls and media remain responsive during heavy updates
  • Optimize background processing: Data loading or computation doesn’t freeze the interface
  • Improve navigation: Switching between screens stays fluid even with complex rendering tasks

Watch this video for an overview of concurrent rendering in Vega:

Hooks, components and helper functions

Two React hooks, useTransition and useDeferredValue, let you manage updates and control your app's performance. Always pair them with component memoization to prevent unnecessary re-renders.

React also provides Suspense component to display fallback components while heavy components render. The lazy helper function lets you lazy-load heavy components and keep them cached for future updates.

useTransition

The useTransition hook allows you to prioritize state updates as immediate or transitional (lower priority and interruptible). It solves a common challenge in search interfaces where filtering operations can delay UI updates. Instead of using techniques like debouncing, you can now mark filtering as lower priority while keeping the search input responsive.

The hook returns two values:

  • isPending: A boolean indicating if lower-priority updates are pending, useful for showing loading states
  • startTransition: A function that wraps state updates you want to treat as non-urgent

Example:

In this code example, while the search input updates immediately with setQuery, the filtering operation is deferred using startTransition, showing a loading indicator during the transition and displaying results when ready.

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // Update search input
    startTransition(() => {
      setResults(performSearch(query); // Defer search operation
    });
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      <SearchResults results={results} />
    </View>
  );
};  

The code shows that startTransition doesn't delay the function execution - it only marks the setResults update as low priority. React processes setQuery first (high priority queue) before handling setResults (low priority queue).

For cases where you can't use the hook directly, use the startTransition function directly.

When to use: Search interfaces, navigation, large list updates where you control the state variables in your component.

When to avoid: Components where you don't have direct state control (use useDeferredValue instead).

useDeferredValue

The useDeferredValue hook lets you defer updating values from props or external sources until after critical updates complete. For example, when a component receives search results, it can immediately update the search term in the header while deferring the heavier work of updating the results preview.

When to use: Props that trigger expensive computations or asynchronous operations when you do not have access to the state variables.

When to avoid: Components where you control the state variables (use useTransition instead)

Suspense and lazy

The Suspense component displays fallback content while code or data loads (becomes ready to use). The lazy function imports components on demand (gets the code when needed). You can use them independently or together.

Use Suspense alone for data fetching

In this example, Suspense shows a loading placeholder (MediaSkeleton) while MediaList fetches and renders genre data:

import { MediaAPI, Media, MediaCard } from "./api/mediaApi.ts";

type Props = { genre: string };

export const MediaList = ({ genre }: Props) => {
  const [media, setMedia] = useState<Media[]>([]);
  useEffect(() => {
    // Long promise resolution time
    MediaAPI.getGenre(genre)
        .then(setMedia)
        .catch((e) => console.error(e));
  }, [genre]);

  if (media.length===0) return null;

  return media.map((media: Media) => (
    <MediaCard key={media.id} media={media} />
  ));
};

Use lazy with Suspense for code splitting with built-in loading states

In this example, MassiveComponent imports when LazyLoadedScreen renders, while Suspense shows a MediaSkeleton placeholder:

const MassiveComponent = lazy(() => import('./MassiveComponent'));

export const LazyLoadedScreen = () => {
  return (
    <View style={styles.container}>
      <Suspense fallback={<MediaSkeleton style={styles.skeleton} />}>
        <MassiveComponent />
      </Suspense>
    </View>
  );
};

Implement concurrent rendering

Follow these steps to implement concurrent rendering:

Step 1: Add component level memoization

Memoization prevents unnecessary re-renders and computations. It is critical for children of components using useTransition() or useDeferredValue(). Without memoization, children re-render when parents update, losing the benefits of concurrent rendering.

Without memoization:

In this example, SearchComponent shows how memoization affects rendering with useTransition. Without memoization, SearchResults re-renders on every keystroke even though results haven't changed.

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // Update search input
    startTransition(() => {
      setResults(performSearch(query)); // Defer search operation
    });
  };

  // SearchResults rerenders on every keystroke
  const SearchResults = ({results}) => {
    return results.map(result => <Card data={result} />);
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      <SearchResults results={results} />
    </View>
  );
};

With memoization:

With memo, SearchResults only re-renders when results change.

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // Update search input
    startTransition(() => {
      setResults(performSearch(query)); // Defer search operation
    });
  };

  // SearchResults only rerenders when results change
  const SearchResults = memo(({results}) => {
    return results.map(result => <Card data={result} />);
  });

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      <SearchResults results={results} />
    </View>
  );
};

Step 2: Apply priority-based updates

Users need immediate feedback for interactions like typing, clicking, or scrolling, while data processing or UI updates can wait. Use useTransition and useDeferredValue to prioritize updates.

Without priority updates:

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = (query) => {
    setQuery(query);                    // Update search input
    setResults(performSearch(query);    // Blocks UI during search
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      <SearchResults results={results} />
    </View>
  );
};

The expensive operation (performSearch) runs immediately after every input update (setQuery), blocking the UI until both updates complete. Users experience lag when typing because each keystroke waits for the search operation to finish.

With priority updates:

const SearchComponent = () => {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query) => {
    setQuery(query);                    // Update search input
    startTransition(() => {
      setResults(performSearch(query); // Defer search operation
    });
  };

  return (
    <View>
      <TextInput
        value={query} 
        onChange={handleSearch} 
      />
      {isPending && <LoadingIndicator />}
      // Don't forget to Memoize!!
      <SearchResults results={results} />
    </View>
  );
};  

The expensive operation (performSearch) now runs in a transition, allowing the input update (setQuery) to complete immediately. Users can type smoothly while search results update in the background, with a loading indicator showing when updates are pending.

Test and validate

Test your concurrent rendering implementation with these tools:

React DevTools Profiler

Use the React DevTools Profiler to test your memoization and priority updates implementation:

  1. Open React DevTools in Chrome.
  2. Select the Profiler tab.
  3. Click the record button.
  4. Perform test interactions (type in search, scroll lists).
  5. Stop recording.
  6. Review the flame graph for unnecessary re-renders and transition markers.

Vega App KPI Visualizer

Use the Vega App KPI Visualizer when testing UI responsiveness.

To run the KPI Visualizer:

kepler exec perf kpi-visualizer --app-name=[app-name] --kpi ui-fluidity --test-scenario <python test scenario with appium>

Look for fluidity scores above 99% to confirm your concurrent rendering implementation maintains smooth UI interactions.


Last updated: Nov 05, 2025