Jose A
Jose A

Reputation: 11129

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

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'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"
}

Upvotes: 17

Views: 27732

Answers (6)

Michael Giovanni Pumo
Michael Giovanni Pumo

Reputation: 14794

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.

Upvotes: 0

anon37894203
anon37894203

Reputation: 545

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.

Upvotes: 1

congdc
congdc

Reputation: 65

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',
    }),
  ],
});

Upvotes: 2

Martin
Martin

Reputation: 363

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

Upvotes: -2

aboutaaron
aboutaaron

Reputation: 5389

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.

Upvotes: 27

Jose A
Jose A

Reputation: 11129

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>
  );
}

Upvotes: 3

Related Questions