◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-10-23Intermediate

A Script to Find the React Native APIs SDK 58 Removed, Before You Upgrade

A small Node script that scans your source for APIs Expo SDK 58 removed: InteractionManager, Touchable, the Modal animated prop, some StatusBar props, and more. Includes sample output and what it cannot catch.

Expo SDK 585React Native241upgradeaudit scriptInteractionManager

The last thing I want the night before an upgrade is to bump the version, meet a red screen, and only then start hunting for the cause. Rereading the SDK 58 announcement on expo.dev/changelog, I noticed that a cluster of React Native APIs had been removed.

Here is what it lists, close to the original wording:

  • InteractionManager is removed (use requestIdleCallback instead)
  • The Touchable export from the react-native root is removed
  • The animated prop on Modal is removed
  • StatusBar loses backgroundColor, translucent, and networkActivityIndicatorVisible
  • Deep imports from react-native/Libraries/* become type errors

Each change is small. But in my own app, which I've maintained for years as an indie developer, I cannot count from memory where each of these still hides. So I wrote a short script that walks the source and prints the matching lines, then ran it against a sample project.

The short version

For removed APIs, one script that keeps all the rules in one place misses less than a handful of grep commands. There are two reasons. Tags such as StatusBar and Modal often spread their props over several lines, which a line-based grep cannot see. And some names, like import { Touchable }, need to be told apart from similar words elsewhere.

What it catches, and what it does not

I drew the line first.

TargetHow it is matchedNote
InteractionManagerThe word itself (comment lines skipped)Reacts to imports and calls alike
Deep importsAny string containing react-native/Libraries/Overlaps with TypeScript errors
TouchableOnly inside the braces of an import from react-nativeDoes not match TouchableOpacity and friends
animated on ModalThe whole opening tag (multi-line is fine)A => inside a prop cuts the match short
Removed StatusBar propsThe whole opening tag (multi-line is fine)Same caveat
libSQL in expo-sqlitesyncLibSQL, libSQLOptions, useLibSQLConfig files need a separate look
File.write()A .write( in a file that imports expo-file-systemReports lines without await

I also decided what it leaves alone. Native-side changes (R8 being enabled, the iOS scene-based life cycle) and the Node.js version requirement cannot be read from source. The last section covers how to check those another way.

The audit script

It depends only on Node. Run node sdk58-removed-api-scan.mjs ./my-app with your project root. It exits with code 1 when anything matches, so it also fits into CI.

// sdk58-removed-api-scan.mjs — Expo SDK 58 で削除・変更された API の使用箇所を洗い出します
// 使い方: node sdk58-removed-api-scan.mjs <プロジェクトのルート>
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join, extname, relative } from 'node:path';
 
const root = process.argv[2] ?? '.';
const EXT = ['.ts', '.tsx', '.js', '.jsx'];
 
const walk = (d) =>
  readdirSync(d).flatMap((n) => {
    if (n === 'node_modules' || n.startsWith('.') || n === 'ios' || n === 'android') return [];
    const p = join(d, n);
    return statSync(p).isDirectory() ? walk(p) : EXT.includes(extname(p)) ? [p] : [];
  });
 
const lineOf = (text, index) => text.slice(0, index).split('\n').length;
 
// 行単位の規則: [名前, 正規表現, 直し方]
const LINE_RULES = [
  ['InteractionManager', /\bInteractionManager\b/, 'requestIdleCallback に置き換えます'],
  ['react-native 深い import', /['"]react-native\/Libraries\//, 'react-native の公開 export へ。急ぐなら tsconfig の customConditions で先送りできます'],
  ['Touchable (ルート export)', /import\s*\{[^}]*\bTouchable\b[^}]*\}\s*from\s*['"]react-native['"]/, 'Pressable などへ置き換えます'],
  ['expo-sqlite libSQL', /\bsyncLibSQL\b|\blibSQLOptions\b|\buseLibSQL\b/, 'libSQL は削除されています。同期の仕組みを別に用意します'],
];
 
// JSX タグ単位の規則: [名前, タグ名, 属性の正規表現, 直し方]
const TAG_RULES = [
  ['Modal の animated', 'Modal', /\banimated\b/, 'animationType に置き換えます'],
  ['StatusBar の削除済み props', 'StatusBar', /\b(backgroundColor|translucent|networkActivityIndicatorVisible)\b/, '削除された props です。背景色は画面側の View に持たせます'],
];
 
let total = 0;
const report = (rel, line, name, fix) => {
  total++;
  console.log(`${rel}:${line}  ${name}  → ${fix}`);
};
 
for (const p of walk(root)) {
  const text = readFileSync(p, 'utf8');
  const rel = relative(root, p);
  const lines = text.split('\n');
  lines.forEach((l, i) => {
    if (/^\s*(\/\/|\*)/.test(l)) return; // コメント行は数えません
    for (const [name, re, fix] of LINE_RULES) if (re.test(l)) report(rel, i + 1, name, fix);
  });
  for (const [name, tag, attr, fix] of TAG_RULES) {
    const tagRe = new RegExp(`<${tag}\\b[^>]*>`, 'g');
    for (const m of text.matchAll(tagRe)) if (attr.test(m[0])) report(rel, lineOf(text, m.index), name, fix);
  }
  // expo-file-system の File.write() は SDK 58 から非同期です
  if (/from\s*['"]expo-file-system['"]/.test(text)) {
    lines.forEach((l, i) => {
      if (/\.write\(/.test(l) && !/await\s/.test(l) && !/\.then\(/.test(l)) {
        report(rel, i + 1, 'File.write() が await されていません', 'await を付けるか、従来の挙動なら writeSync() を使います');
      }
    });
  }
}
console.log(total ? `\n${total} 件` : '該当なし');
process.exitCode = total ? 1 : 0;

The rules live in two arrays: LINE_RULES for line-based checks and TAG_RULES for JSX tags. I made the tag rules work on whole tags so that a <StatusBar that runs over three lines before its /> still gets caught. When a new item appears, you add one line to an array.

Running it on a sample

I made two files with removed APIs mixed in on purpose, plus an empty project with nothing in it. The InteractionManager inside a comment and the File.write() that already has await are meant to be ignored.

app/home.tsx:1  InteractionManager  → requestIdleCallback に置き換えます
app/home.tsx:2  Touchable (ルート export)  → Pressable などへ置き換えます
app/home.tsx:6  InteractionManager  → requestIdleCallback に置き換えます
app/home.tsx:13  Modal の animated  → animationType に置き換えます
app/home.tsx:9  StatusBar の削除済み props  → 削除された props です。背景色は画面側の View に持たせます
app/store.ts:2  react-native 深い import  → react-native の公開 export へ。急ぐなら tsconfig の customConditions で先送りできます
app/store.ts:7  File.write() が await されていません  → await を付けるか、従来の挙動なら writeSync() を使います
 
7 件

The hint text on the right is in Japanese because the script was written for my own use; translate the strings if you share it with a team. The empty project printed "該当なし" (no matches) and exited with 0. The same run confirmed that the comment line and the awaited line were not counted.

Keep in mind that this is the result on a small sample I prepared, not a count from a real app. How many hits your own app produces is something only a run on your machine can tell you.

How I read the output, and the order I fix things

I settle the order like this:

  1. Type errors first (deep imports). Adding "react-native-legacy-deep-imports" to customConditions in tsconfig.json defers the problem, but the announcement says that escape hatch is set to be removed after React Native 0.89. I treat the deferral as a one-time loan
  2. Behavior changes next (File.write() becoming async). Forget the await and the next step starts before the write finishes. Nothing looks wrong on screen; it shows up later as a file that reads back empty
  3. Visual changes last (StatusBar, Modal), since checking them on a real device doubles as the visual test

Changes the source cannot reveal

Three things this script cannot see, written down so they are not forgotten:

  • Node.js: Expo tooling needs 22.13+, 24.3+, or 26+, and the odd-numbered 23 and 25 are unsupported. Check with node -v
  • R8: Release builds now default to android.enableMinifyInReleaseBuilds=true. Libraries that rely on reflection may need ProGuard keep rules. To opt out, set it to false through expo-build-properties
  • iOS scene life cycle: Prebuild generates SceneDelegate.swift and a UIApplicationSceneManifest entry in Info.plist. If you edit AppDelegate.swift by hand, follow the official migration steps

I have also written up checks for other SDK 58 changes. The environment-variable rules are in Expo SDK 58 No Longer Loads .env.staging. A 70-Line Script to Audit Your .env Files, the build trouble for projects using Stripe is in Expo SDK 58 With Stripe: pod install Stops, and Whether to Turn Off Precompiled Modules or Wait, and how many dependency recommendations move after one bump is counted in I counted how many versions expo install starts recommending after one patch bump.

Count the removed names before you upgrade, not after something breaks. It takes much of the guesswork out of upgrade day.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◇ App Dev2026-10-22
Expo SDK 58 No Longer Loads .env.staging. A 70-Line Script to Audit Your .env Files
From SDK 58, Expo commands skip custom files like .env.staging and overwrite any NODE_ENV you pass in. A small Node script that shows which .env files load and which line wins, run against a sample project.
◇ App Dev2026-10-21
expo-widgets Works on iOS but Fails Only in the Android Build — Pin the Version or Exclude It?
Reports say expo-widgets 58.0.17 and later stops at Kotlin compilation on Android only. How to recognize it, when to pin versus exclude from autolinking, and a script that checks your project.
◇ App Dev2026-10-15
When adjustsFontSizeToFit Shrinks Just One Row, Suspect the Box Height, Not the String
Only the 10th row of a list drops to 4pt on iOS, though the data and strings are fine. I read the Yoga rounding and font-fit sources, then counted how often the box comes up short in Node.