react-nativemonorepoasyncstorageyarn-workspaces

Cannot read property 'setItem' of undefined for @react-native-async-storage/async-storage


Im building a yarn 3 monorepo using react native for the app. I have an app and a ui package. Inside the ui package Im using AsyncStorage to set a value like the following

import AsyncStorage from "@react-native-async-storage/async-storage";

export const ThemeProvider: React.FC<IProvider> = () => {
    useEffect(() => {
    const storeValue = async () => {
      try {
        await AsyncStorage.setItem("value", JSON.stringify("test"));
      } catch (error) {
        console.log(error);
      }
    };

    storeValue();
  }, []);

...
}

Now when I import and use this code via my lib workspace. I get the following error [TypeError: Cannot read property 'setItem' of undefined]

Yes I have @react-native-async-storage/async-storage installed on both app and the ui workspaces

if I use await AsyncStorage.setItem("value", JSON.stringify("test")); directly in the app it works fine. Its just if the code is coming from the dependency (my ui workspace)

UPDATE

Here is what my metro looks like thats in the react-native workspace

const { makeMetroConfig } = require('@rnx-kit/metro-config')
const MetroSymlinksResolver = require('@rnx-kit/metro-resolver-symlinks')
const { getDefaultConfig } = require('metro-config')
const exclusionList = require('metro-config/src/defaults/exclusionList')
const path = require('path')

module.exports = async () => {
  const {
    resolver: { sourceExts, assetExts },
  } = await getDefaultConfig()

  return makeMetroConfig({
    resolver: {
      resolveRequest: MetroSymlinksResolver(),
      symlinks: false,
      blockList: exclusionList([
        /^((?!rnapp).)+[\/\\]node_modules[/\\]react-native[/\\].*/,
        /ui[\/\\]node_modules[/\\]react-native-svg[/\\].*/,
        /ui[\/\\]node_modules[/\\]react-native-safe-area-context[/\\].*/,
      ]),
      extraNodeModules: {
        // Resolve all react-native module imports to the locally-installed version
        'react-native': path.resolve(__dirname, 'node_modules', 'react-native'),

        // Resolve additional nohoist modules depended on by other packages
        'react-native-svg': path.resolve(__dirname, 'node_modules', 'react-native-svg'),
        'react-native-safe-area-context': path.resolve(__dirname, 'node_modules', 'react-native-safe-area-context'),
        '@react-native-async-storage/async-storage': path.resolve(
          __dirname,
          'node_modules',
          '@react-native-async-storage/async-storage'
        ),

        // Resolve core-js imports to the locally installed version
        'core-js': path.resolve(__dirname, 'node_modules', 'core-js'),
      },

      assetExts: assetExts.filter(ext => ext !== 'svg'),
      sourceExts: [...sourceExts, 'svg'],
    },
    transformer: {
      babelTransformerPath: require.resolve('react-native-svg-transformer'),
      getTransformOptions: async () => ({
        transform: {
          experimentalImportSupport: false,
          inlineRequires: true,
        },
      }),
    },
  })
}

File Structure

├── apps
│   └── rnapp
│       ├── src
│       └── package.json
├── packages
│   └── ui
│       ├── src
│       ├── dist (generated by rollup, also ignored by git)
│       └── package.json
├── README.md
└── package.json

rnapp package.json

{
  "name": "@app/rnapp",
  "packageManager": "yarn@3.5.1",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start --reset-cache",
    "test": "jest"
  },
  "dependencies": {
    "@app/ui": "1.0.0",
    "@react-native-async-storage/async-storage": "^1.18.1",
    "@react-native-community/cli-platform-ios": "^11.3.0",
    "@react-navigation/bottom-tabs": "^6.5.7",
    "@react-navigation/native": "^6.1.6",
    "@react-navigation/native-stack": "^6.9.12",
    "@reduxjs/toolkit": "^1.9.5",
    "date-fns": "^2.30.0",
    "humps": "^2.0.1",
    "lottie-ios": "3.4.0",
    "lottie-react-native": "^5.1.6",
    "qs": "^6.11.1",
    "react": "18.2.0",
    "react-native": "0.71.8",
    "react-native-flipper": "^0.190.0",
    "react-native-gesture-handler": "^2.10.1",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-safe-area-context": "4.5.3",
    "react-native-screens": "^3.20.0",
    "react-native-sensitive-info": "^5.5.8",
    "react-native-svg": "^13.9.0",
    "react-redux": "^8.0.5",
    "redux": "^4.2.1",
    "redux-deep-persist": "^1.0.7",
    "redux-flipper": "^2.0.2",
    "redux-persist": "^6.0.0",
    "redux-persist-sensitive-storage": "^1.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.20.0",
    "@babel/plugin-syntax-flow": "^7.21.4",
    "@babel/plugin-transform-react-jsx": "^7.21.5",
    "@babel/preset-env": "^7.20.0",
    "@babel/runtime": "^7.20.0",
    "@react-native-community/eslint-config": "^3.2.0",
    "@rnx-kit/metro-config": "^1.3.6",
    "@rnx-kit/metro-resolver-symlinks": "^0.1.28",
    "@tsconfig/react-native": "^2.0.2",
    "@types/humps": "^2.0.2",
    "@types/jest": "29.2.1",
    "@types/qs": "^6.9.7",
    "@types/react": "18.0.24",
    "@types/react-test-renderer": "18.0.0",
    "babel-jest": "^29.2.1",
    "babel-plugin-module-resolver": "5.0.0",
    "eslint": "^8.19.0",
    "eslint-config-prettier": "8.8.0",
    "eslint-import-resolver-typescript": "3.5.5",
    "eslint-plugin-flowtype": "8.0.3",
    "eslint-plugin-import": "2.27.5",
    "eslint-plugin-module-resolver": "1.5.0",
    "eslint-plugin-prettier": "4.2.1",
    "eslint-plugin-react": "7.32.2",
    "eslint-plugin-react-hooks": "4.6.0",
    "get-yarn-workspaces": "^1.0.2",
    "jest": "^29.2.1",
    "metro-config": "^0.76.4",
    "metro-react-native-babel-preset": "0.73.9",
    "prettier": "^2.4.1",
    "react-native-dotenv": "3.4.8",
    "react-native-svg-transformer": "^1.0.0",
    "react-test-renderer": "18.2.0",
    "typescript": "4.8.4"
  },
  "jest": {
    "preset": "react-native"
  }
}

ui package.json

{
  "name": "@app/ui",
  "packageManager": "yarn@3.5.1",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "module": "dist/index.js",
  "private": true,
  "scripts": {
    "build": "rm -rf ./dist && rollup -c",
    "storybook": "storybook dev",
    "storybook:build": "storybook build"
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "^1.18.1",
    "@react-native-community/hooks": "^3.0.0",
    "@rollup/plugin-typescript": "^11.1.1",
    "lottie-ios": "3.4.0",
    "lottie-react-native": "^5.1.6",
    "react": "^18.2.0",
    "react-native": "0.71.8",
    "react-native-pixel-perfect": "^1.0.2",
    "react-native-safe-area-context": "4.5.3",
    "react-native-svg": "^13.9.0"
  },
  "devDependencies": {
    "@babel/core": "^7.21.4",
    "@babel/plugin-transform-modules-commonjs": "^7.21.2",
    "@babel/preset-env": "^7.21.5",
    "@babel/preset-react": "^7.18.6",
    "@babel/preset-typescript": "^7.21.5",
    "@babel/runtime": "^7.20.0",
    "@react-native-community/datetimepicker": "^7.0.1",
    "@react-native-community/eslint-config": "^3.2.0",
    "@react-native-community/slider": "^4.4.2",
    "@rollup/plugin-babel": "^6.0.3",
    "@rollup/plugin-commonjs": "^25.0.0",
    "@rollup/plugin-json": "^6.0.0",
    "@rollup/plugin-node-resolve": "^15.0.2",
    "@storybook/addon-actions": "^7.0.12",
    "@storybook/addon-controls": "^7.0.12",
    "@storybook/addon-docs": "^7.0.12",
    "@storybook/addon-essentials": "^7.0.12",
    "@storybook/addon-interactions": "^7.0.12",
    "@storybook/addon-jest": "^7.0.12",
    "@storybook/addon-links": "^7.0.12",
    "@storybook/addon-mdx-gfm": "^7.0.12",
    "@storybook/addon-react-native-web": "^0.0.20",
    "@storybook/api": "^7.0.12",
    "@storybook/cli": "^7.0.12",
    "@storybook/react": "^7.0.12",
    "@storybook/react-native": "^6.5.3",
    "@storybook/react-webpack5": "^7.0.12",
    "@testing-library/jest-native": "^5.4.2",
    "@testing-library/react": "^14.0.0",
    "@testing-library/react-native": "^12.0.1",
    "@types/jest": "^29.2.1",
    "@types/react": "^18.0.24",
    "@types/react-test-renderer": "^18.0.0",
    "babel-jest": "^29.5.0",
    "babel-loader": "^8.3.0",
    "babel-plugin-react-native-web": "^0.19.2",
    "eslint": "^8.19.0",
    "eslint-config-prettier": "^8.8.0",
    "eslint-import-resolver-typescript": "^3.5.5",
    "eslint-plugin-flowtype": "^8.0.3",
    "eslint-plugin-import": "^2.27.5",
    "eslint-plugin-module-resolver": "^1.5.0",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "jest": "^29.2.1",
    "jest-environment-jsdom": "^29.5.0",
    "react-dom": "^18.2.0",
    "react-native-web": "~0.18.10",
    "react-test-renderer": "18.2.0",
    "rollup": "^3.22.0",
    "rollup-plugin-terser": "^7.0.2",
    "storybook": "^7.0.12",
    "storybook-dark-mode": "^3.0.0",
    "typescript": "^5.0.4"
  },
  "eslintIgnore": [
    "node_modules/",
    "dist/"
  ],
  "prettierIgnore": [
    "node_modules/",
    "dist/"
  ]
}

root package.json

{
  "name": "@app/monorepo",
  "packageManager": "yarn@3.5.1",
  "private": true,
  "scripts": {
    "clean": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + && rm -rf apps/rnapp/ios/Pods",
    "app:start": "yarn workspace @app/rnapp run start",
    "app:pod": "cd apps/rnapp/ios && pod install",
    "ui:build": "yarn workspace @app/ui run build",
    "ui:storybook": "yarn workspace @app/ui run storybook"
  },
  "workspaces": [
    "apps/rnapp",
    "packages/ui"
  ]
}

.yarnrc

yarnPath: .yarn/releases/yarn-3.5.1.cjs
nodeLinker: node-modules
nmHoistingLimits: workspaces

Solution

  • So I found a example of a monorepo similar to my use case that helped me find my issue.

    Turns out I needed my shared package (ui package) to use peerDependencies instead of dependencies in package.json.

    https://github.com/crutchcorn/react-native-monorepo-example/tree/main