Search code examples
react-nativebabeljsreact-native-reanimatedmetro-bundler

react-native-reanimated: onChange is not a function


I face issues with react-native-reanimated:

import Animated from 'react-native-reanimated';

console.log(Animated.onChange) // returns undefined

I'm using https://reactnavigation.org/docs/material-top-tab-navigator/ and receive the following error when running my app:

TypeError: onChange is not a function. (In 'onChange(_this.gesturesEnabled, cond(not(_this.gesturesEnabled), call([_this.gesturesEnabled], _this.toggleEnabled)))', 'onChange' is undefined)

This error is located at:
    in Pager (created by TabView)
    in RCTView (at View.js:34)
    in View (created by TabView)
    in TabView (at MaterialTopTabView.tsx:51)
    in MaterialTopTabView (at createMaterialTopTabNavigator.tsx:44)
    in MaterialTopTabNavigator (at Screen/index.tsx:21)

It's no issue with the library itself (I can see that the library exports the onChange method as part of the Animated namespace correctly), however I have a feeling that something's wrong with the metro configuration for my react native project (or typescript or babel config, I'm a little lost). Honestly I'm no expert with metro or babel, so any hint is highly appreciated.

package.json

{
  "name": "project",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "react-native start",
    "test": "jest --runInBand --detectOpenHandles",
  },
  "dependencies": {
    "@react-native-community/async-storage": "^1.12.1",
    "@react-native-community/geolocation": "^2.0.2",
    "@react-native-community/masked-view": "^0.1.10",
    "@react-native-firebase/analytics": "^8.0.1",
    "@react-native-firebase/app": "^9.0.0",
    "@react-native-firebase/auth": "^9.3.5",
    "@react-native-firebase/crashlytics": "^8.5.2",
    "@react-native-firebase/messaging": "^8.0.1",
    "@react-native-firebase/perf": "^7.4.12",
    "@react-navigation/material-bottom-tabs": "^5.3.9",
    "@react-navigation/material-top-tabs": "^5.3.9",
    "@react-navigation/native": "^5.8.9",
    "@react-navigation/stack": "^5.12.6",
    "@welldone-software/why-did-you-render": "^6.0.0-rc.1",
    "axios": "^0.21.0",
    "babel-plugin-wildcard": "^6.0.0",
    "color": "^3.1.3",
    "deepmerge": "^4.2.2",
    "equentry-frontend-shared": "^2.0.3",
    "i18next": "^19.8.3",
    "lodash": "^4.17.20",
    "metro-config": "^0.64.0",
    "react": "17.0.1",
    "react-i18next": "^11.7.3",
    "react-native": "0.63.3",
    "react-native-gesture-handler": "^1.8.0",
    "react-native-localize": "^2.0.0",
    "react-native-maps": "^0.27.1",
    "react-native-paper": "^4.4.0",
    "react-native-reanimated": "^1.13.1",
    "react-native-safe-area-context": "^3.1.9",
    "react-native-screens": "^2.14.0",
    "react-native-svg": "^12.1.0",
    "react-native-swipe-gestures": "^1.0.5",
    "react-native-tab-view": "^2.15.2",
    "react-native-vector-icons": "^7.1.0",
    "react-redux": "^7.2.2",
    "redux": "^4.0.5",
    "redux-persist": "^6.0.0",
    "redux-saga": "^1.1.3"
  },
  "devDependencies": {
    "@babel/cli": "^7.12.1",
    "@babel/core": "^7.12.3",
    "@babel/runtime": "^7.12.5",
    "@react-native-community/eslint-config": "^2.0.0",
    "@testing-library/dom": "^7.26.6",
    "@testing-library/react-hooks": "^3.4.2",
    "@testing-library/react-native": "^7.1.0",
    "@testing-library/user-event": "^12.2.2",
    "@types/color": "^3.0.1",
    "@types/jest": "^26.0.15",
    "@types/lodash": "^4.14.165",
    "@types/react": "^16.9.56",
    "@types/react-native": "^0.63.35",
    "@types/react-native-vector-icons": "^6.4.6",
    "@types/react-redux": "^7.1.11",
    "@types/react-test-renderer": "^16.9.3",
    "babel-jest": "^26.6.3",
    "babel-plugin-i18next-extract": "^0.8.2",
    "detox": "^17.11.4",
    "eslint": "^7.13.0",
    "eslint-config-airbnb": "^18.2.1",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "git-branch-is": "^4.0.0",
    "husky": "^4.3.0",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^26.6.3",
    "jest-circus": "^26.6.3",
    "jest-junit": "^12.0.0",
    "jscpd": "^3.3.19",
    "madge": "^3.12.0",
    "metro-react-native-babel-preset": "^0.64.0",
    "react-native-bundle-visualizer": "^2.2.1",
    "react-native-svg-transformer": "^0.14.3",
    "react-test-renderer": "17.0.1",
    "typedoc": "^0.19.2",
    "typedoc-plugin-external-module-name": "^4.0.3",
    "typescript": "^4.0.5"
  }
}

metro.config.js (I'm using react-native-svg-transformer in my project):

const { getDefaultConfig } = require('metro-config');

module.exports = (async () => {
  const {
    resolver: { sourceExts, assetExts },
  } = await getDefaultConfig();
  return {
    transformer: {
      babelTransformerPath: require.resolve('react-native-svg-transformer'),
    },
    resolver: {
      assetExts: assetExts.filter((ext) => ext !== 'svg'),
      sourceExts: [...sourceExts, 'svg'],
    },
  };
})();

tsconfig.json

{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "jsx": "react",
    "lib": ["es6"],
    "moduleResolution": "node",
    "noEmit": true,
    "strict": true,
    "target": "esnext",
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "alwaysStrict": true,
    "noUnusedLocals": true /* Report errors on unused locals. */,
    "noUnusedParameters": true /* Report errors on unused parameters. */,
    "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
    "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  },
  "exclude": [
    "node_modules",
    "babel.config.js",
    "metro.config.js",
    "jest.config.js",
    "jest.setup.js",
    "i18n"
  ]
}

babel.config.js

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    [
      'i18next-extract',
      {
        locales: ['en', 'de'],
        defaultNS: 'default',
        customUseTranslationHooks: [['./src/hooks', 'useTranslation']],
        outputPath: './i18n/translations/{{locale}}/{{ns}}.json',
        defaultValue: '<translate me>',
        useI18nextDefaultValue: true,
      },
    ],
    [
      'wildcard',
      {
        exts: ['json', ''],
        nostrip: true,
        noModifyCase: true,
      },
    ],
  ],
};

Any hint is highly appreciated, this is giving me headaches. :x

Thanks!


Solution

  • Ok, I figured it out by comparing a fresh react native app with only react-native-reanimated installed and my original project.

    As expected the issue was with the babel configuration, especially the wildcard plugin. I couldn't exactly figure out the reason, but applying the plugin to the whole project caused some side effects, i.e. import * as abc from './somelocation in my node_modules not being treated correctly.

    As I need the wildcard plugin only to be applied to a specific folder ./src/i18n/* I changed my babel.config.js as follows:

    module.exports = {
      presets: ['module:metro-react-native-babel-preset'],
      plugins: [
        [
          'i18next-extract',
          {
            locales: ['en', 'de'],
            defaultNS: 'default',
            customUseTranslationHooks: [['./src/hooks', 'useTranslation']],
            outputPath: './src/i18n/translations/{{locale}}/{{ns}}.json',
            defaultValue: '<translate me>',
            useI18nextDefaultValue: true,
          },
        ],
      ],
      overrides: [
        {
          test: './src/i18n/*',
          plugins: [
            [
              'wildcard',
              {
                exts: ['json', ''],
                nostrip: true,
                noModifyCase: true,
              },
            ],
          ],
        },
      ],
    };