Skip to content

make async storage optional for react-native - #2781

Merged
stopachka merged 3 commits into
instantdb:mainfrom
pvinis:optional-async-storage
Jul 21, 2026
Merged

make async storage optional for react-native#2781
stopachka merged 3 commits into
instantdb:mainfrom
pvinis:optional-async-storage

Conversation

@pvinis

@pvinis pvinis commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

right now @instantdb/react-native hard-imports @react-native-async-storage/async-storage at the top of Storage.native.ts, so apps that pass a custom Store to init (mmkv from #2219, expo-sqlite from #2780) still have to install async storage just to make the bundle resolve, and expo doctor errors on the missing peer dep.

this makes the default store lazy and optional:

  • async storage installed: default store, same as today. no behavior change for existing apps, same keys, same data.
  • not installed: fall back to @instantdb/react-native-mmkv, then @instantdb/expo-sqlite, if one of those is installed. installing a wrapper package is enough, no need to pass Store explicitly (though you still can).
  • nothing installed and no custom Store: init throws with instructions, instead of the bundler failing on a module it can't resolve.

the requires sit directly inside try blocks, which metro treats as optional dependencies (transformer.allowOptionalDependencies, enabled by default in both @expo/metro-config and @react-native/metro-config), so bundling no longer fails when async storage is absent. the peer dep is also marked optional via peerDependenciesMeta, which is what fixes the expo doctor error. the async storage store is still exported as AsyncStorageStore if anyone wants to force it.

one caveat to flag: on a metro config that does not enable allowOptionalDependencies (vanilla metro without the expo or rn presets), the two wrapper requires would now fail to resolve for apps that don't have them installed. both standard configs enable it, so this should be a non-issue in practice, but mentioning it in case you'd rather guard differently.

verified the resolution logic in node with stubbed modules (all four combinations pass: async storage wins when present, mmkv then expo-sqlite as fallbacks, helpful error when nothing is there), and build + test:ci pass. also dropped async storage from the mmkv readme install line and added a note to the rn docs page. no lockfile change needed.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1bfd1964-b8c0-4112-824c-7551e663ed5e

📥 Commits

Reviewing files that changed from the base of the PR and between e4b390f and 027d454.

📒 Files selected for processing (1)
  • client/packages/react-native/src/Storage.native.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/packages/react-native/src/Storage.native.ts

📝 Walkthrough

Walkthrough

React Native storage now treats AsyncStorage as optional, loads it dynamically when available, and uses a fallback that reports a configuration error when it is absent. Installation guidance and documentation were updated, and the shared version changed to v1.0.51.

Changes

React Native storage fallback

Layer / File(s) Summary
Runtime storage resolution
client/packages/react-native/package.json, client/packages/react-native/src/Storage.native.ts
AsyncStorage is optional, loaded at runtime, and replaced by MissingStore when unavailable.
Package guidance and release metadata
client/packages/react-native-mmkv/README.md, client/www/app/docs/start-rn/page.md, client/packages/version/src/version.ts
Installation and custom-store documentation no longer require AsyncStorage, and the shared version is updated to v1.0.51.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StorageModule
  participant AsyncStorage
  participant MissingStore
  StorageModule->>AsyncStorage: Attempt optional require
  alt AsyncStorage available
    AsyncStorage-->>StorageModule: Select AsyncStorageStore
  else AsyncStorage unavailable
    StorageModule->>MissingStore: Select fallback class
    MissingStore-->>StorageModule: Throw configuration error on construction
  end
Loading

Suggested reviewers: stopachka

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: making AsyncStorage optional for React Native.
Description check ✅ Passed The description is detailed and directly describes the optional AsyncStorage support changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
client/packages/react-native/src/Storage.native.ts (1)

8-11: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Safely resolve the AsyncStorage module export.

Consider using the mod.default ?? mod pattern here to match how you're resolving @instantdb/react-native-mmkv and @instantdb/expo-sqlite on lines 94 and 98. Depending on a user's specific bundler or module resolution configuration, require might return the module object directly without a .default property. If this happens, AsyncStorage will be set to undefined, which silently triggers the fallback and throws the MissingStore error even when the package is correctly installed.

♻️ Proposed refactor
 let AsyncStorage: any = null;
 try {
-  AsyncStorage = require('`@react-native-async-storage/async-storage`').default;
+  const mod = require('`@react-native-async-storage/async-storage`');
+  AsyncStorage = mod.default ?? mod;
 } catch {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/packages/react-native/src/Storage.native.ts` around lines 8 - 11,
Update the AsyncStorage initialization in the module-loading try block to retain
the required module object when no default export exists, using the same
default-or-module resolution pattern as the react-native-mmkv and expo-sqlite
loaders. Preserve the existing fallback behavior when the require itself fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@client/packages/react-native/src/Storage.native.ts`:
- Around line 8-11: Update the AsyncStorage initialization in the module-loading
try block to retain the required module object when no default export exists,
using the same default-or-module resolution pattern as the react-native-mmkv and
expo-sqlite loaders. Preserve the existing fallback behavior when the require
itself fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f4ebe5ee-21bd-4ce5-8051-e11fe050bd7c

📥 Commits

Reviewing files that changed from the base of the PR and between 0844747 and f07af87.

📒 Files selected for processing (5)
  • client/packages/react-native-mmkv/README.md
  • client/packages/react-native/package.json
  • client/packages/react-native/src/Storage.native.ts
  • client/packages/version/src/version.ts
  • client/www/app/docs/start-rn/page.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
client/packages/react-native/src/Storage.native.ts (1)

85-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add isolated coverage for both default-store branches.

Mock the optional dependency before importing the module and verify that the default export is AsyncStorageStore when present and MissingStore when absent; also cover init with an explicit custom Store.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@client/packages/react-native/src/Storage.native.ts` at line 85, Add isolated
tests for the default export in Storage.native, mocking the optional
AsyncStorage dependency before each module import to verify AsyncStorageStore
when available and MissingStore when unavailable. Also test init with an
explicitly supplied custom Store, preserving the existing default-store
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/packages/react-native/src/Storage.native.ts`:
- Around line 5-10: Update the AsyncStorage loading fallback in
Storage.native.ts so it only treats a genuinely absent optional module as
MissingStore; rethrow other errors from the require, including native, linking,
or version failures. Preserve the direct require placement inside the try block
so Metro continues recognizing AsyncStorage as optional, and keep custom Store
handling unchanged.

---

Nitpick comments:
In `@client/packages/react-native/src/Storage.native.ts`:
- Line 85: Add isolated tests for the default export in Storage.native, mocking
the optional AsyncStorage dependency before each module import to verify
AsyncStorageStore when available and MissingStore when unavailable. Also test
init with an explicitly supplied custom Store, preserving the existing
default-store behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0fa0f969-4657-4ab3-96e7-ed3dbb9805ec

📥 Commits

Reviewing files that changed from the base of the PR and between f07af87 and 0d3b089.

📒 Files selected for processing (2)
  • client/packages/react-native/src/Storage.native.ts
  • client/packages/version/src/version.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/packages/version/src/version.ts

Comment thread client/packages/react-native/src/Storage.native.ts Outdated
pvinis and others added 2 commits July 21, 2026 16:10
Which store an app persists to shouldn't depend on what happens to be in
node_modules: installing async storage for an unrelated library would
silently flip an auto-detected mmkv app back to AsyncStorage, stranding
any unsynced offline mutations. So if async storage isn't installed,
init now throws with instructions instead of probing for wrapper
packages. Wrapper stores keep working by passing Store to init, which is
the documented path.

Also bumps the version to v1.0.51 since v1.0.50 already shipped with the
expo-sqlite adapter.
The optional require swallows every error, so an installed async-storage
whose native side isn't linked would produce a misleading "install
async-storage" message at init. Rethrowing isn't an option (it would
crash apps that pass a custom Store at import time), and Metro gives no
reliable way to tell "absent" from "broken", so instead we capture the
require error and append it to the MissingStore message. A half-linked
install now shows async-storage's own error with its rebuild
instructions.
@stopachka

Copy link
Copy Markdown
Contributor

Great work @pvinis .

I like this PR in general. I removed the part where we auto-detect the different storage families.

Main reason was about predictability:

i.e Imagine installing @instantdb/react-native-mmkv. But then, you also install react-native-async-storage for something else. You may unexpectedly switch to react-native-async-storage.

This keeps react-native-async storage the only default, but keeps the logic to let you omit it if you are using a custom store.

@stopachka
stopachka merged commit c8640e5 into instantdb:main Jul 21, 2026
29 of 30 checks passed
@pvinis

pvinis commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

thanks for taking a look!

fair. my thought was that maybe we add a thing in config for which one to use. would that maybe be better? so if I just have mmkv and don't want asyncstore, I set it up to use mmkv?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants