Suspense complaining about startTransition, but I am using it

227 Views Asked by At

I've done a simple menu/detail example in ReactNative with Suspense and startTransition

import React, {useState, Suspense, startTransition} from 'react';
import {
  Button,
  Details,
  Heading,
  Menu,
  Text,
  Wrapper,
} from './StarWarsSuspenseApp.styled';
import {QueryClient, QueryClientProvider, useQuery} from 'react-query';

const api = (entity: 'planets') => `https://swapi.dev/api/${entity}`;
const EMPTY = '';

type Planet = {
  name: string;
  rotation_period: string;
  orbital_period: string;
  diameter: string;
  climate: string;
  gravity: string;
  terrain: string;
  surface_water: string;
  population: string;
  residents: string[];
  films: string[];
  created: string;
  edited: string;
  url: string;
};

type ApiResponse<K> = {
  count: number;
  next: string;
  previous: null;
  results: K;
};

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
    },
  },
});

const fetchWithErrors = async <T,>(request: Promise<Response>): Promise<T> => {
  const response = await request;
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json() as Promise<T>;
};

const fetchPlanets = () =>
  fetchWithErrors<ApiResponse<Planet[]>>(fetch(api('planets')));
const usePlanets = () => useQuery('planets', fetchPlanets);

const fetchPlanetDetails = (url: string) =>
  fetchWithErrors<ApiResponse<Planet>>(fetch(url));
const usePlanetDetails = (url: string) =>
  useQuery(['planet', url], () => fetchPlanetDetails(url), {
    enabled: url !== EMPTY,
  });

const Screen = () => {
  const [selectedUrl, setSelectedUrl] = useState(EMPTY);
  const planets = usePlanets();
  const planetDetails = usePlanetDetails(selectedUrl);
  const handlePlanetSelect = (url: string) => {
    startTransition(() => {
      setSelectedUrl(url);
    });
  };

  return (
    <Wrapper>
      <Suspense fallback={<Heading>Loading Planets...</Heading>}>
        {planets.isLoading ? (
          <Heading>Loading...</Heading>
        ) : planets.isError ? (
          <Heading>{`${planets.error}`}</Heading>
        ) : (
          <Menu>
            {planets?.data?.results.map(planet => (
              <Button
                key={planet.name}
                title={planet.name}
                onPress={() => handlePlanetSelect(planet.url)}
              />
            ))}
          </Menu>
        )}
      </Suspense>
      {selectedUrl !== EMPTY && (
        <Suspense fallback={<Heading>Loading Planet Details...</Heading>}>
          {planetDetails.isLoading ? (
            <Heading>Loading Details...</Heading>
          ) : planetDetails.isError ? (
            <Heading>{`${planetDetails.error}`}</Heading>
          ) : (
            <Details>
              <Text>{JSON.stringify(planetDetails.data, null, 2)}</Text>
            </Details>
          )}
        </Suspense>
      )}
    </Wrapper>
  );
};

const StarWarsSuspenseApp = () => (
  <QueryClientProvider client={queryClient}>
    <Screen />
  </QueryClientProvider>
);

export default StarWarsSuspenseApp;

But I get this error

Error: A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.

This error is located at:
    in Screen (created by StarWarsSuspenseApp)
    in QueryClientProvider (created by StarWarsSuspenseApp)
    in StarWarsSuspenseApp (created by Routes)
    in RenderedRoute (created by Routes)
    in Routes (created by App)
    in Router (created by MemoryRouter)
    in MemoryRouter (created by NativeRouter)
    in NativeRouter (created by App)
    in RCTSafeAreaView (created by SafeAreaView)
    in Styled(RCTSafeAreaView) (created by App)
    in App
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in Toy(RootComponent), js engine: hermes

Packages

"react": "18.2.0",
"react-native": "0.72.7",
"react-query": "3.39.3",

Can anyone see the problem?

1

There are 1 best solutions below

1
On

The handlePlanetSelect function is called in response to a user action and should be wrapped in startTransition:

const handlePlanetSelect = (url: string) => {
  startTransition(() => {
    setSelectedUrl(url);
  });
};