ReferenceError: React is not defined - Migrating from CRA to Vite and NX
Asked Answered
C

6

16

I'm currently in the process of migrating a create-react-app (CRA - v4) monorepo Webpack setup to an NX Monorepo powered by Vite.

I'm currently stuck trying to figure out how to solve the typical

Uncaught ReferenceError: React is not defined

This happens whenever a file doesn't import React directly, but has a named import from it, such as:

import { memo } from 'react';

I've run the linter that removed all the import React statements, and it'd be daunting to through hundreds and hundreds of files to add it again.

Here's more info:

  • I believe I'm using the newest JSX transform and React 17.
  • nx-plugin-vite: ^1.1.0
  • vite: ^2.7.1
  • vite-tsconfig-paths: ^3.3.17
  • vite-plugin-eslint: ^1.3.0
  • @vitejs/plugin-react: ^1.1.3
  • @nrwl/react": 13.2.4, (More in the package.json)

I've also read and read again several sources across GitHub, SO, and the web and haven't found anything

Here's my vite.config.ts

import path from 'path';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import eslintPlugin from 'vite-plugin-eslint';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [tsconfigPaths(), eslintPlugin(), react()],
  resolve: {
    alias: {
      stream: 'stream-browserify',
      '~': path.resolve(__dirname, 'src'),
    },
  },
  server: {
    open: true,
  },
});

Here's my tsconfig.json:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": true,
    "skipLibCheck": false,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "noFallthroughCasesInSwitch": true
  },
  "include": ["./src"]
}

And the base tsconfig.json:

{
  "compileOnSave": false,
  "compilerOptions": {
    "rootDir": ".",
    "sourceMap": true,
    "isolatedModules": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "importHelpers": true,
    "target": "es2015",
    "module": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "baseUrl": ".",
    "paths": {
      "@schon/legacy-components/*": ["apps/app/src/components/*"],
      "@schon/graphql/*": ["apps/app/src/graphql/*"],
      "@schon/hooks/*": ["libs/components/src/hooks/*"],
      "@schon/components/*": ["libs/components/src/ad/*"],
      "@schon/legacy-components2/*": ["libs/components/src/stories/*"],
      "@schon/theme": ["libs/components/src/styles/index.ts"],
      "@schon/typings": ["libs/components/src/typings/index.ts"],
      "@schon/utils": ["libs/components/src/utils/index.ts"],
      "~/*": ["apps/app/src/*"]
    }
  },
  "exclude": ["node_modules", "tmp"]
}

project.json:

{
  "root": "apps/app",
  "projectType": "application",
  "sourceRoot": "apps/app/src",
  "tags": [],
  "targets": {
    "serve": {
      "executor": "nx-plugin-vite:serve",
      "options": {
        "configFile": "apps/app/vite.config.ts",
        "port": 3001,
        "host": false,
        "https": false
      }
    },
    "preview": {
      "executor": "nx-plugin-vite:preview",
      "options": {
        "configFile": "apps/app/vite.config.ts"
      }
    },
    "build": {
      "executor": "nx-plugin-vite:build",
      "options": {
        "outDir": "dist",
        "configFile": "apps/app/vite.config.ts",
        "watch": false,
        "write": true,
        "emitAtRootLevel": false,
        "manifest": true
      }
    }
  }
}

Here's one of the files that is giving me problems (Throws me the React is not defined): (This one comes from a component repo that is being handled by storybook)

import { memo } from 'react';
import { Container as MaterialContainer } from '@material-ui/core';
import { ThemeSpecs } from '../../../styles/theme';

type ContainerProps = {
  children?:
    | JSX.Element
    | JSX.Element[]
    | React.ReactNode
    | React.ReactChildren;
  className?: string;
};

/**
 * Jose decided to wrap this up, in case we needed to apply a general styling to the container
 * itself, and avoid repeating it in every other component.
 */
const Component: React.FC<ContainerProps> = (props) => (
  <MaterialContainer
    className={props.className}
    fixed
    style={{ paddingTop: ThemeSpecs.container.paddingTop }}
  >
    {props.children!}
  </MaterialContainer>
);

type withContainerProps = {};

/**
 * This is a HOC so we can use this to Containerize the imports back
 * at root. This way we can choose which routes use Containers
 * and which don't.
 */

export const withContainer = <P extends object>(
  ComponentToContainer: React.ComponentType<P>
) =>
  class WithContainer extends React.PureComponent<P & withContainerProps> {
    render() {
      return (
        <Container>
          <ComponentToContainer {...this.props} />
        </Container>
      );
    }
  };

export const Container = memo(Component) as typeof Component;

package.json

{
  "scripts": {
    "start": "nx serve",
    "build": "nx build",
    "test": "nx test"
  },
  "private": true,
  "dependencies": {
    "@apollo/client": "^3.5.6",
    "@auth0/auth0-react": "^1.8.0",
    "@aws-sdk/client-s3": "^3.44.0",
    "@date-io/date-fns": "^2.11.0",
    "@material-table/core": "^4.3.11",
    "@material-ui/core": "^4.12.3",
    "@material-ui/icons": "^4.11.2",
    "@material-ui/lab": "^4.0.0-alpha.60",
    "@material-ui/pickers": "^3.3.10",
    "@material-ui/system": "^4.12.1",
    "@nivo/calendar": "^0.74.0",
    "@nivo/core": "^0.74.0",
    "@nivo/line": "^0.74.0",
    "@nivo/tooltip": "^0.74.0",
    "@reach/router": "^1.3.4",
    "auth0-js": "^9.18.0",
    "aws-appsync-auth-link": "^3.0.7",
    "aws-appsync-subscription-link": "^3.0.9",
    "aws-sdk": "^2.1046.0",
    "clsx": "^1.1.1",
    "core-js": "^3.6.5",
    "d3-array": "^3.1.1",
    "date-fns": "^2.27.0",
    "dotenv": "^10.0.0",
    "exceljs": "^4.3.0",
    "file-saver": "^2.0.5",
    "formik": "^2.2.9",
    "formik-persist": "^1.1.0",
    "framer-motion": "^5.4.5",
    "fraql": "^1.2.1",
    "graphql": "^16.1.0",
    "husky": "^7.0.4",
    "immer": "^9.0.7",
    "linkifyjs": "^3.0.4",
    "lodash": "^4.17.21",
    "logrocket": "^2.1.2",
    "material-table": "^1.69.3",
    "msw": "^0.36.3",
    "password-validator": "^5.2.1",
    "randomcolor": "^0.6.2",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-dropzone-uploader": "^2.11.0",
    "react-elastic-carousel": "^0.11.5",
    "react-error-boundary": "^3.1.4",
    "react-google-docs-viewer": "^1.0.1",
    "react-icons": "^4.3.1",
    "react-intersection-observer": "^8.32.5",
    "react-lazy-load-image-component": "^1.5.1",
    "react-loading-skeleton": "^3.0.1",
    "react-prerendered-component": "^1.2.4",
    "regenerator-runtime": "0.13.7",
    "stream-browserify": "^3.0.0",
    "styled-components": "^5.3.3",
    "suneditor": "^2.41.3",
    "suneditor-react": "^3.3.1",
    "sw-precache": "^5.2.1",
    "tiny-slider-react": "^0.5.3",
    "tslib": "^2.0.0",
    "use-debounce": "^7.0.1",
    "uuid": "^8.3.2",
    "validate-password": "^1.0.4",
    "yup": "^0.32.11"
  },
  "devDependencies": {
    "@angular-devkit/schematics": "^13.0.4",
    "@babel/core": "7.12.13",
    "@babel/preset-typescript": "7.12.13",
    "@nrwl/cli": "13.2.4",
    "@nrwl/cypress": "13.2.4",
    "@nrwl/eslint-plugin-nx": "13.2.4",
    "@nrwl/jest": "13.2.4",
    "@nrwl/linter": "13.2.4",
    "@nrwl/node": "^13.2.4",
    "@nrwl/nx-cloud": "latest",
    "@nrwl/react": "13.2.4",
    "@nrwl/storybook": "^13.3.0",
    "@nrwl/tao": "^13.2.4",
    "@nrwl/web": "13.2.4",
    "@nrwl/workspace": "^13.2.4",
    "@nxext/react": "^13.0.0",
    "@snowpack/plugin-dotenv": "^2.2.0",
    "@snowpack/plugin-react-refresh": "^2.5.0",
    "@snowpack/plugin-typescript": "^1.2.1",
    "@snowpack/web-test-runner-plugin": "^0.2.2",
    "@storybook/addon-actions": "^6.4.9",
    "@storybook/addon-essentials": "~6.3.0",
    "@storybook/addon-knobs": "^6.4.0",
    "@storybook/addon-links": "^6.4.9",
    "@storybook/addon-storysource": "^6.4.9",
    "@storybook/builder-webpack5": "~6.3.0",
    "@storybook/manager-webpack5": "~6.3.0",
    "@storybook/react": "~6.3.0",
    "@svgr/webpack": "^5.4.0",
    "@testing-library/react": "12.1.2",
    "@testing-library/react-hooks": "7.0.2",
    "@types/auth0-js": "^9.14.5",
    "@types/chai": "^4.2.21",
    "@types/jest": "27.0.2",
    "@types/mocha": "^9.0.0",
    "@types/node": "14.14.33",
    "@types/react": "17.0.30",
    "@types/react-dom": "17.0.9",
    "@types/react-lazy-load-image-component": "^1.5.2",
    "@types/snowpack-env": "^2.3.4",
    "@types/tiny-slider-react": "^0.3.3",
    "@types/uuid": "^8.3.3",
    "@types/yup": "^0.29.13",
    "@typescript-eslint/eslint-plugin": "~4.33.0",
    "@typescript-eslint/parser": "~4.33.0",
    "@vitejs/plugin-react": "^1.1.3",
    "@web/test-runner": "^0.13.17",
    "babel-jest": "27.2.3",
    "babel-loader": "8.1.0",
    "chai": "^4.3.4",
    "cypress": "^8.3.0",
    "eslint": "7.32.0",
    "eslint-config-prettier": "8.1.0",
    "eslint-plugin-cypress": "^2.10.3",
    "eslint-plugin-import": "2.25.2",
    "eslint-plugin-jsx-a11y": "6.4.1",
    "eslint-plugin-react": "7.26.1",
    "eslint-plugin-react-hooks": "4.2.0",
    "jest": "27.2.3",
    "nx-plugin-snowpack": "^0.3.0",
    "nx-plugin-vite": "^1.1.0",
    "prettier": "^2.3.1",
    "react-test-renderer": "17.0.2",
    "snowpack": "^3.8.8",
    "storybook-theme-toggle": "^0.1.2",
    "ts-jest": "27.0.5",
    "typescript": "~4.4.3",
    "url-loader": "^3.0.0",
    "vite": "^2.7.1",
    "vite-plugin-eslint": "^1.3.0",
    "vite-preset-react": "^2.2.0",
    "vite-tsconfig-paths": "^3.3.17"
  }
}

The nx.json:

{
  "npmScope": "schon",
  "affected": {
    "defaultBase": "main"
  },
  "cli": {
    "defaultCollection": "@nrwl/react"
  },
  "implicitDependencies": {
    "package.json": {
      "dependencies": "*",
      "devDependencies": "*"
    },
    ".eslintrc.json": "*"
  },
  "tasksRunnerOptions": {
    "default": {
      "runner": "@nrwl/nx-cloud",
      "options": {
        "cacheableOperations": ["build", "lint", "test", "e2e"],
      }
    }
  },
  "targetDependencies": {
    "build": [
      {
        "target": "build",
        "projects": "dependencies"
      }
    ]
  },
  "generators": {
    "@nrwl/react": {
      "application": {
        "style": "css",
        "linter": "eslint",
        "babel": true
      },
      "component": {
        "style": "css"
      },
      "library": {
        "style": "css",
        "linter": "eslint"
      }
    }
  },
  "defaultProject": "app"
}
Cotidal answered 29/12, 2021 at 12:49 Comment(0)
H
23

I also received ReferenceError: React is not defined when porting my app from create-react-app to vite. The issue stemmed from CRA using the new jsx runtime to auto-import React into jsx files.

Vite doesn't use the new jsx runtime by default but you can easily add it by installing the @vitejs/plugin-react:

# install the plugin
yarn add @vitejs/plugin-react

...and then updating vite.config.js:

// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()]
})

Note: @vitejs/plugin-react also includes react-refresh so you should be able to remove @vitejs/plugin-react-refresh if you're using it.

Hooligan answered 15/2, 2022 at 23:3 Comment(4)
Thanks!!! I did include it in the question! (Please double check!). The problem was that I was using React.Fragment (directly calling React), when in fact I either needed to have it in scope or destruct the fragment.Cotidal
For me this isn't working, but I only get the error on builds.Mullane
This is such a lifesaver, thank you so much!Josephson
Yep that was it! Thank you!Fanny
C
3

I found the problem. Apparently there was a moment in which I stripped out all of the import React statements. But beware, you can't just blindly remove it if you don't destruct Children, Fragment, or others.

For example:

if you have:

import React, {memo} from 'react';

const MyElem = () => {
  const myMemo = useMemo(() => {}, []);
  return (
       <React.Fragment>
       </React.Fragment>
  );
}

and you remove React, you will have the dependency Fragment lingering around.

You will have to import it, or destruct the Fragment

Meaning, that you will have to do this:

import {Fragment, memo} from 'react';

const MyElem = () => {
  const myMemo = useMemo(() => {}, []);
  return (
       <Fragment>
       </Fragment>
  );
}
Cotidal answered 29/12, 2021 at 16:52 Comment(2)
Glad you found the solution. As one of the developers of @nxext. I can confirm standard introduce by NX was to auto inject React using tsconfig "jsx": "react-jsx", As a result we followed this standard for the vite builder. If people want to manually import react then you can just remove JSX line from tsconfigProfundity
Oooooohhhh. Glad to know that Jordan!! Thank you so much. Nx has been incredible! Appreciate all the hard work put by the team!!!Cotidal
S
2

I found a problem. For new apps and React 17 projects onwards, using jsxRuntime: 'automatic' is recommended as it provides many benefits in terms of code size and performance. However, if you are working on an old project or want to keep the classic syntax, you can use jsxRuntime: 'classic' or not use a jsxRuntime configuration if you want to keep the defaults.

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      jsxRuntime: 'automatic',
    }),
  ],
});
Siva answered 16/1, 2024 at 14:17 Comment(1)
This is also the default value so calling the react() plugin without arguments does the same jobAmbit
S
1

In my case, I wasn't using nx but had the same problem.

Your vite.config.js file must be at the same level of your package.json, otherwise the config won't be read and you won't see any warnings, but you'll get the React is not defined error.

Stephi answered 29/2, 2024 at 10:18 Comment(0)
P
0

I'm late but I got this issue too and had to make a setting change in .tsconfig.json file for it to work. jsx was previously set to "preserve" but it needed to be "react-jsx".

{
  "compilerOptions": {
    "jsx": "react-jsx"
    // All your other settings...
  }
}

Hope this helps someone in the future.

Pitapat answered 7/4, 2024 at 23:33 Comment(0)
H
-3

Adding a import React from 'react'; import at the top of the component test file fixed it for me

Hemlock answered 3/7, 2023 at 17:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.