This version now depends on React 18. As part of this change, the @raycast/api package now has strong dependencies on @types/react and @types/node so those 2 packages shall be removed from your extension's devDependencies (this will be done automatically by npx ray migrate).

There are no breaking changes introduced in React 18 so all your extensions will still work properly. However, there are some breaking changes in @types/react so you might have to do some minor tweaks if you use TypeScript.

We had a pass through all the extensions on the public repo. Notably, if you are using raycast-toolkit or swr, you will have to update them to their latest version.

React Suspense

Suspense is an advanced feature of React. You do not need to use or understand it to build Raycast extensions (or any React application). We are not going to teach Suspense here but we will highlight how you can work with it in Raycast.

You can now use Suspense in Raycast. We provide a top-level Suspense fallback so you don't have to. This fallback simply turns on the isLoading on the top level view.

import { List, Toast, showToast, ActionPanel, Action, Icon, popToRoot } from "@raycast/api";
import { useState, useCallback } from "react";
import * as twitter from "./oauth/twitter";

// a hook that suspends until a promise is resolved
import { usePromise } from "./suspense-use-promise";

const promise = async (search: string) => {
  try {
    await twitter.authorize();
    return await twitter.fetchItems(search);
  } catch (error) {
    showToast({ style: Toast.Style.Failure, title: String(error) });
    return [];

export default function Command() {
  const [search, setSearch] = useState("");

  const items = usePromise(promise, [search]);

  const logout = useCallback(() => {
  }, []);

  const actionPanel = (
      <Action title="Logout" onAction={logout} />

  // no need to set the `isLoading` prop, Raycast will set it automatically
  // until the React application isn't suspended anymore
  return (
    <List onSearchTextChange={setSearch} throttle>
      {items.map((item) => {
        return (
          <List.Item key={item.id} id={item.id} icon={Icon.TextDocument} title={item.title} actions={actionPanel} />

Last updated