Skip to content

MMKV does not persist data. #1030

Closed
Closed

Description

@CD-Z

First of all I already saw #1011 but it couldn't help me.

Recently I tried upgrading a bare app with epxo modules to expo 55 and rn 83 from 54 and 81. However after getting the app running again mmkv doesn't persist the data. I was now debugging this problem for three days but still have no clue on solving this.

I also tried creating a smaller example app, but no matter what i did it was always working.

I tried:

  • Removing custom plugins
  • Removing the appIdSuffix
  • Upgrading to the latest mmkv version
  • removing the scr folder
  • copying the android folder in a new app
  • build the project on a different pc
  • build it on an emulator
  • build it on an actual phone
  • ...

But still the new app was always working and the original wasn't.
I don't know if I have hit some edgecase or just do something wrong, but I am out of ideas and hope someone here can help me.

Also what I noticed is that mmkv doesn't create a new folder or read the existing one. Other packages like op-sqlite continue working normally, even though they write in the same folder.

Here is the PR to upgrade the app: CD-Z/lnreader#28

Activity

  1. maintenance-hans commented on Mar 30, 2026

    @maintenance-hans

    Guten Tag, Hans here.

    Note

    New features, bugfixes, updates and other improvements are all handled mostly by @mrousavy in his free time.
    To support @mrousavy, please consider 💖 sponsoring him on GitHub 💖.
    Sponsored issues will be prioritized.

  2. WeberG619 commented on Mar 30, 2026

    @WeberG619

    Hi,

    I see you've spent three days debugging MMKV persistence after upgrading to Expo 55 / RN 0.83 — and frustratingly, it works fine in a fresh app but not in the original. The fact that op-sqlite continues working in the same directory while MMKV doesn't even create its folder suggests something in your native build config or module resolution changed during the upgrade.

    I'm a full-stack developer working across Python, C#, and TypeScript daily — building everything from REST APIs and database systems to desktop applications and automation pipelines.

    What I deliver:

    • Clean, tested code with proactive communication — no surprises
    • Experience with JavaScript and React Native debugging in production environments
    • Fast turnaround — I typically have a working first version within days, not weeks

    For this issue:
    I'd start by diffing your android/build.gradle, gradle.properties, and AndroidManifest.xml against the working sample app to isolate what's breaking MMKV's native module initialization. Then trace the actual file I/O calls to see where MMKV thinks it should write versus where your app's sandboxed storage lives.

    Can start immediately. Have you checked whether MMKV's native module is actually loading, or does MMKV.initialize() silently fail?

    Weber Gouin

  3. devappsstudio commented on Mar 31, 2026

    @devappsstudio

    Hi,

    I encountered the issue ‘MMKV does not persist data’ after I upgraded my Expo project to Expo 55:
    "expo": "55.0.5",
    "react": "19.2.0",
    "react-native": "0.83.2",
    "react-native-mmkv": "4.3.0",
    "react-native-nitro-modules": "0.35.2",
    ... ,

    and here is my solution that i use like react-native-mmkv+4.3.0.patch:

    index 9342961..751b248 100644
    --- a/node_modules/react-native-mmkv/src/isTest.ts
    +++ b/node_modules/react-native-mmkv/src/isTest.ts
    @@ -4,6 +4,7 @@ export function isTest(): boolean {
         return false
       }
       return (
    -    process.env.JEST_WORKER_ID != null || process.env.VITEST_WORKER_ID != null
    +    (process.env.JEST_WORKER_ID != null || process.env.VITEST_WORKER_ID != null) &&
    +    process.env.NODE_ENV === 'test'
       )
     }

    this helped, but more precise testing is needed in different cases, and it would be great to hear thought on this from @mrousavy , thanks in advance, and I will be glad if this helps someone.

  4. riteshshukla04 commented on Mar 31, 2026

    @riteshshukla04
    Member

    Hey @CD-Z , Thanks for the issue. The issue is not in react-native-mmkv but in how ENVs are read .

    react-native-dotenv is setting JEST_WORKER_ID to be a truthy value which makes createMMKV to run the Mock env .
    For quick fix you can patch dot env to something like below .

    --- a/node_modules/react-native-dotenv/index.js                                                             
      +++ b/node_modules/react-native-dotenv/index.js
      @@ -152,7 +152,9 @@ module.exports = (api, options) => {                                                    
                 const key = path.toComputedKey()                                                               
                 if (t.isStringLiteral(key)) {                                                                  
                   const importedId = key.value
      -            const value = (env && importedId in env) ? env[importedId] : process.env[importedId]
      +            // FIX: Only inline values from .env files, never fall back to build-time process.env
      +            // (build-time process.env leaks JEST_WORKER_ID from Metro's jest-worker)
      +            const value = (env && importedId in env) ? env[importedId] : undefined
                   if (value !== undefined) {
                     path.replaceWith(t.valueToNode(value))
                   }
    
  5. CD-Z commented on Mar 31, 2026

    @CD-Z
    Author

    Hey @riteshshukla04, thank you very much for your help. I didn't even think it could be an issue with env variables. I changed from react-native-dotenv to react-native-config as dotenv hasn't been updated in 2 years and I don't want to deal with patch files. Now it is working perfectly fine again.

    Maybe react-native-mmkv should change the isTest method to something more robust like @devappsstudio suggested or add a warning log that it is now in test mode. For this reason I will keep the issue open even though my problem is solved.

  6. mrousavy commented on Mar 31, 2026

    @mrousavy
    Member

    Maybe react-native-mmkv should change the isTest method

    react-native-mmkv is open source

  7. CD-Z commented on Mar 31, 2026

    @CD-Z
    Author

    I know and I didn't mean to demand anything but in my opinion this is something that should be decided by someone who is used to this project and knows how to solve this situation without creating new problems.

  8. mrousavy commented on Apr 1, 2026

    @mrousavy
    Member

    Makes sense!

    After chatting about this a bit I decided to close this issue - this is not related to react-native-mmkv and should probably be fixed in the dotenv package

  9. kKaskak commented on May 23, 2026

    @kKaskak

    @mrousavy i had the exact same issue with the storage not being persisted after upgrading the expo version, mmkv pkg version did not change, i migrated from the react-native-dotenv to expo native env solution with EXPO_PUBLIC prefix
    ref doc: https://docs.expo.dev/guides/environment-variables/

    and the issue persisted i had to make the same patch as @riteshshukla04 and add a patch script

  10. mrousavy commented on May 23, 2026

    @mrousavy
    Member

    Can you reproduce this via a Harness test in this repo?

  11. kKaskak commented on May 23, 2026

    @kKaskak

    Can you reproduce this via a Harness test in this repo?

    investigated and i could not reproduce, i rebuild everything again and the patch was not necessary anymore, probably cache issue

    thank you all works well now without the react-native-dotenv package

  12. riteshshukla04 commented on May 23, 2026

    @riteshshukla04
    Member

    I just want to point out that that the fix
    (process.env.JEST_WORKER_ID != null || process.env.VITEST_WORKER_ID != null) && process.env.NODE_ENV === 'test' is not correct . Its sad that LLM generates this.

    This is how expo determines the testing env .
    https://github.com/expo/expo/blob/31d4a6cc20eb3c1c6c015ed30b02b9b1b3119769/packages/expo-notifications/src/index.ts#L3-L5
    NODE_ENV may or may not be test in JEST

  13. kKaskak commented on May 23, 2026

    @kKaskak

    I just want to point out that that the fix
    (process.env.JEST_WORKER_ID != null || process.env.VITEST_WORKER_ID != null) && process.env.NODE_ENV === 'test' is not correct . Its sad that LLM generates this.

    This is how expo determines the testing env .
    https://github.com/expo/expo/blob/31d4a6cc20eb3c1c6c015ed30b02b9b1b3119769/packages/expo-notifications/src/index.ts#L3-L5
    NODE_ENV may or may not be test in JEST

    To get it working before removing the package I had to apply your suggestion yes, the other one did not work. Thank you I tagged the wrong person lol

  14. praisedavid787 commented on Sep 3, 2026

    @praisedavid787

    Same root cause as #1011 — I posted the full write-up there, short version here since this issue describes it precisely ("MMKV didn't create a new folder or read the existing one, while op-sqlite kept working in the same folder"). That is exactly the signature: the native store is never touched at all.

    isTest() returns true in ordinary Expo builds, so createMMKV() returns createMockMMKV() — an in-memory Map. Reads and writes succeed, getAllKeys() works, no file is ever created, and everything is empty on next launch.

    Why:

    1. Metro parallelises transforms with jest-worker, which sets JEST_WORKER_ID in each worker process.
    2. babel-preset-expo inlines static process.env.X member expressions at transform time, including inside node_modules.
    3. The transform worker's id is therefore baked into the app bundle as a literal.

    Grepping the compiled bundle for isTest:

    function isTest() {
      if (global.process == null) { return false; }
      return Boolean("6") || Boolean(process.env.VITEST_WORKER_ID);
    }

    "6" is Metro transform worker #6. JEST_WORKER_ID is unset in the shell, in .env*, and in the Metro process — it only exists inside the transform worker.

    Corroborating: with MMKV_logLevel=0, MMKV's core logs initialisation but never an instance — no loadFromFile, no mmap, no open fd — and existsMMKV(id) is false for every store. Both Nitro's Logger and MMKV's core are silent at default log levels, which is why this is so hard to pin down.

    Fix — read the env through a variable so Babel cannot inline it:

    const env = global.process.env || {};
    return Boolean(env['JEST_WORKER_ID'] || env['VITEST_WORKER_ID']);

    Jest still sets JEST_WORKER_ID at runtime, so genuine test runs keep the mock. Verified on device (Expo 57.0.15 / RN 0.86.2 / mmkv 4.3.2 / metro 0.84.5): all stores now create <id> + <id>.crc, and after a force-stop + relaunch, loaded [base-storage] with 10 key-values / loaded [redux-storage] with 1 key-values. Before the patch they were empty on every launch.

    This affects any Expo app on mmkv v4, in debug and release alike, and it fails silently.

    Applying this today (until it's fixed upstream)

    With patch-package:

    # 1. edit both files in node_modules
    #    node_modules/react-native-mmkv/lib/isTest.js   (this is what gets bundled)
    #    node_modules/react-native-mmkv/src/isTest.ts   (keep in sync)
    # 2. generate the patch
    npx patch-package react-native-mmkv
    # 3. ensure it re-applies on install
    #    package.json -> "scripts": { "postinstall": "patch-package" }
    # 4. rebuild native + clear Metro cache
    npx react-native start --reset-cache

    lib/isTest.js is the one that matters — package.json has "main": "lib/index", so that is what Metro bundles. Patching only src/isTest.ts will appear to do nothing.

    Two gotchas worth flagging:

    • Clear the Metro cache. Editing files inside node_modules does not reliably invalidate the transform cache, so the old (mocked) module can keep being served.
    • Do not verify with globalThis.process?.env?.JEST_WORKER_ID. Optional chaining defeats Babel's inlining, so it reads undefined at runtime while the library's static read sees the baked-in worker id. I chased that false negative for hours. Check by grepping the served bundle instead:
    curl -s 'http://localhost:8081/index.bundle?platform=android&dev=true&minify=false' \
      | grep -o 'function isTest[^}]*}[^}]*}'

    If you see Boolean("<number>") or "<number>" != null, you are getting the mock.

  15. mrousavy commented on Sep 7, 2026

    @mrousavy
    Member

    Doesn't react-native-reanimated do the same check? That process.env. stuff seems hacky?

  16. riteshshukla04 commented on Sep 7, 2026

    @riteshshukla04
    Member

    I just want to point out that that the fix (process.env.JEST_WORKER_ID != null || process.env.VITEST_WORKER_ID != null) && process.env.NODE_ENV === 'test' is not correct . Its sad that LLM generates this.

    This is how expo determines the testing env . https://github.com/expo/expo/blob/31d4a6cc20eb3c1c6c015ed30b02b9b1b3119769/packages/expo-notifications/src/index.ts#L3-L5 NODE_ENV may or may not be test in JEST

    Adding to what Marc mentioned ,Please read this comment . Even expo does the same like MMKV . This shouldn’t land in Mmkv but fixed in maybe dotenv

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions