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 in trying to figure out how to solve the typical

Uncaught ReferenceError: React is not defined

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

import { memo } from 'react';

I’ve ran the linter that removed all the import React statements, and I’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"
}