41

Summary of Problem:

Without changing my code over this weekend some class components started giving me the following error.

'EverySelector' cannot be used as a JSX component.
  Its element type 'ReactElement<any, any> | Component<Omit<EverySelectorProps, keyof WithTranslation>, any, any> | null' is not a valid JSX element.
    Type 'Component<Omit<EverySelectorProps, keyof WithTranslation>, any, any>' is not assignable to type 'Element | ElementClass | null'.
      Type 'Component<Omit<EverySelectorProps, keyof WithTranslation>, any, any>' is not assignable to type 'ElementClass'.
        The types returned by 'render()' are incompatible between these types.
          Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react-transition-group/node_modules/@types/react/index").ReactNode'.ts(2786)

This is not the case for every class component. I will show examples in Show Some Code section.

When asking my colleague to do a npm i and npm run build on one of his older branches it passes and he doesn't have any error. When I checkout his branch and do the same, it still gives me the same error. I've cleaned my cache and I've done a clean install on his branch.

What I've Tried

  1. I assumed that one of my packages is breaking me. So I removed all ^ from package.json: "redux": "^4.0.5" to "redux": "4.0.5". However the problem still persisted.
  2. Based on Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react-transition-group/node_modules/@types/react/index").ReactNode'.ts(2786) I started playing around with the versions of @types/react and @types/react-dom.
  3. Played around with exports. One of the first components I noticed was breaking was the <Header/> which has the following export
export default withRouter(
  connect(mapStateToProps, actions)(withStyles(styles)(withTranslation()(Header)))
);

Which I changed to

export default connect(mapStateToProps, actions)(withStyles(styles)(withTranslation()(Header)));

This made the <Header/> stop throwing the error but I noticed numerous other similar errors which were not related to withRouter which leads me to believe that the problem is not related to react-router-dom. Afterwards, the error I received was in index.tsx:

ReactDOM.render(
  <Provider store={store}>
    <HashRouter>
      <App style={{ width: '100%', height: '100%' }} />
    </HashRouter>
  </Provider>,
  document.getElementById('root')
);
'Provider' cannot be used as a JSX component.
  Its instance type 'Provider<AnyAction>' is not a valid JSX element.
    The types returned by 'render()' are incompatible between these types.
      Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react-transition-group/node_modules/@types/react/index").ReactNode'.
'HashRouter' cannot be used as a JSX component.
  Its instance type 'HashRouter' is not a valid JSX element.
    The types returned by 'render()' are incompatible between these types.
      Type 'React.ReactNode' is not assignable to type 'import(".../node_modules/@types/react-transition-group/node_modules/@types/react/index").ReactNode'.ts(2786)

Show Some Code

This is a broken component EverySelector:

//EverySelector.tsx
import React, { ChangeEvent, Component, ReactNode } from 'react';
import { FormControl, InputLabel, MenuItem, Select, SelectChangeEvent } from '@mui/material';
import { WithTranslation, withTranslation } from 'react-i18next';

class EverySelector extends Component<EverySelectorProps> {
  private handleOnEveryChange = (event: SelectChangeEvent<number>, child: ReactNode): void => {
    event.target.value && this.props.onEveryChanged(event.target.value as number);
  };

  private generateDropdownOptions = (): number[] => {
    let options = [1, 2, 3, 5, 10, 15, 30, 60];
    const roundHoursToBeIncluded = [1, 2, 4, 8, 12, 24, 48];

    roundHoursToBeIncluded.map((roundHour: number) => {
      if ((roundHour * 60) % this.props.confEvery === 0) {
        const roundHourOption = (roundHour * 60) / this.props.confEvery;
        if (!options.includes(roundHourOption)) {
          options.push(roundHourOption);
        }
      }
    });

    return options.sort((a: number, b: number) => a - b);
  };

  private convertMinutesToString = (totalMinutes: number): string => {
    const days = Math.floor(totalMinutes / 1440); // 60 * 24
    const hours = Math.floor((totalMinutes - days * 1440) / 60);
    const minutes = Math.round(totalMinutes % 60);

    let output = '';

    if (days > 0) {
      output += days + ' ' + `${days === 1 ? this.props.t('day') : this.props.t('days')}`;
    }
    if (hours > 0) {
      output += `${days > 0 ? ' ' : ''}${hours} ${
        hours === 1 ? this.props.t('hour') : this.props.t('hours')
      }`;
    }
    if (minutes > 0) {
      output += `${days > 0 || hours > 0 ? ' ' : ''}${minutes} ${
        minutes === 1 ? this.props.t('minute') : this.props.t('minutes')
      }`;
    }

    return output;
  };

  public render(): React.ReactNode {
    return (
      <FormControl variant="outlined" size="small">
        <InputLabel>Every</InputLabel>
        <Select
          id="every_selector"
          value={this.props.every}
          onChange={this.handleOnEveryChange}
          fullWidth
          autoWidth
          label="Every"
        >
          {this.generateDropdownOptions().map((i: number) => (
            <MenuItem value={i}>{this.convertMinutesToString(this.props.confEvery * i)}</MenuItem>
          ))}
        </Select>
      </FormControl>
    );
  }
}

interface EverySelectorProps extends WithTranslation {
  confEvery: number;
  every: number;
  onEveryChanged: (every: number) => void;
}

export default withTranslation()(EverySelector);

This is a working component DateSelectorAverage:

//DateSelectorAverage.tsx
import { Grid, TextField } from '@mui/material';
import moment from 'moment';
import React, { ChangeEvent, Component } from 'react';
import { connect } from 'react-redux';
import {
  AvgFromDatePeriodAction,
  AvgToDatePeriodAction,
} from '../../../state_management/actions/PeriodSelectionAction';
import { PeriodSelectionActionCreator } from '../../../state_management/actions/PeriodSelectionActionCreator';

class DateSelectorAvg extends Component<DateSelectorAvgProps> {
  public render(): React.ReactNode {
    return (
      <Grid container spacing={3} direction="row">
        <Grid item>
          <TextField
            label="Average From Date"
            type="date"
            size="small"
            variant="outlined"
            value={moment(this.props.avgFromDate).format('YYYY-MM-DD')}
            onChange={(event: ChangeEvent<HTMLInputElement>): void => {
              this.props.periodAvgFromDateChanged(new Date(event.target.value));
            }}
            InputLabelProps={{
              shrink: true,
            }}
          />
        </Grid>
        <Grid item>
          <TextField
            label="Average To Date"
            type="date"
            size="small"
            variant="outlined"
            value={moment(this.props.avgToDate).format('YYYY-MM-DD')}
            onChange={(event: ChangeEvent<HTMLInputElement>): void => {
              this.props.periodAvgToDateChanged(new Date(event.target.value));
            }}
            InputLabelProps={{
              shrink: true,
            }}
          />
        </Grid>
      </Grid>
    );
  }
}

interface DateSelectorAvgProps extends DateSelectorAvgOwnProps, StateProps, DispatchProps {}

interface DateSelectorAvgOwnProps {}

interface StateProps {
  avgFromDate: Date;
  avgToDate: Date;
}

interface DispatchProps {
  periodAvgFromDateChanged: (date: Date | null) => AvgFromDatePeriodAction;
  periodAvgToDateChanged: (date: Date | null) => AvgToDatePeriodAction;
}

const mapStateToProps = (stateProps: { period: StateProps }): StateProps => ({
  avgFromDate: stateProps.period.avgFromDate,
  avgToDate: stateProps.period.avgToDate,
});

const mapDispatchToProps: DispatchProps = {
  periodAvgFromDateChanged: PeriodSelectionActionCreator.changeAvgFromDate,
  periodAvgToDateChanged: PeriodSelectionActionCreator.changeAvgToDate,
};

export default connect(mapStateToProps, mapDispatchToProps)(DateSelectorAvg);

package.json

{
  "name": "device-counter-collector-gui",
  "version": "0.1.1-SNAPSHOT",
  "private": true,
  "devDependencies": {
    "@types/chart.js": "2.9.32",
    "@types/googlemaps": "3.30.16",
    "@types/jest": "26.0.19",
    "@types/lodash": "4.14.171",
    "@types/markerclustererplus": "2.1.33",
    "@types/node": "14.14.16",
    "@types/react": "17.0.39",
    "@types/react-dom": "17.0.11",
    "@types/react-redux": "7.1.14",
    "@types/react-router-dom": "5.1.6",
    "@typescript-eslint/eslint-plugin": "4.11.1",
    "@typescript-eslint/parser": "4.11.1",
    "eslint-config-prettier": "7.1.0",
    "eslint-plugin-prettier": "3.3.0",
    "eslint-plugin-react": "7.21.5",
    "prettier": "2.2.1",
    "react-router-prop-types": "1.0.5",
    "redux-devtools-extension": "2.13.5"
  },
  "dependencies": {
    "@date-io/date-fns": "1.3.13",
    "@emotion/react": "11.7.1",
    "@emotion/styled": "11.6.0",
    "@mui/icons-material": "5.4.2",
    "@mui/lab": "5.0.0-alpha.69",
    "@mui/material": "5.4.2",
    "@mui/styles": "5.4.2",
    "acorn-jsx": "5.0.0",
    "axios": "0.14.0",
    "chart.js": "2.9.4",
    "chartjs-plugin-zoom": "0.7.7",
    "colorbrewer": "1.0.0",
    "date-fns": "2.28.0",
    "dotenv-cli": "4.1.0",
    "eslint": "7.17.0",
    "formik": "2.2.9",
    "i18next": "21.6.12",
    "i18next-browser-languagedetector": "3.0.1",
    "jwt-decode": "2.2.0",
    "lodash": "4.17.21",
    "material-ui-popup-state": "1.0.1",
    "moment": "2.18.1",
    "moment-timezone": "0.5.33",
    "node-polyfill-webpack-plugin": "1.1.4",
    "path-browserify": "1.0.1",
    "path-to-regexp": "1.7.0",
    "pondjs": "0.8.5",
    "react": "17.0.2",
    "react-autobind": "1.0.6",
    "react-bootstrap": "0.32.4",
    "react-bootstrap-table": "4.3.1",
    "react-chartjs-2": "2.11.1",
    "react-device-detect": "2.0.1",
    "react-dom": "16.8.6",
    "react-easy-chart": "1.0.0",
    "react-file-download": "0.3.3",
    "react-flexbox-grid": "1.0.2",
    "react-ga": "3.3.0",
    "react-google-maps": "9.4.5",
    "react-google-maps-loader": "4.2.3",
    "react-i18next": "11.15.5",
    "react-icons": "4.3.1",
    "react-jss": "8.6.1",
    "react-recaptcha-google": "1.1.1",
    "react-redux": "7.1",
    "react-router-dom": "5.3.0",
    "react-scripts": "5.0.0",
    "react-select": "1.0.0-rc.2",
    "react-timeseries-charts": "0.15.5",
    "redux": "4.0.5",
    "redux-thunk": "2.2.0",
    "typescript": "4.5.2",
    "uuid": "2.0.3",
    "webpack": "4.44.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "lint": "eslint . --ext .js --ext .ts --ext .tsx",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "proxy": "http://localhost:8080",
  "homepage": "./",
  "module": {
    "noParse": "./~/object-hash/dist/object_hash.js"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}
Wizard-of-Koz
  • 1,217
  • 1
  • 10
  • 13

5 Answers5

56

I updated react & react-dom.

npm i react@18 react-dom@18 @types/react@18 @types/react-dom@18
Wizard-of-Koz
  • 1,217
  • 1
  • 10
  • 13
23

The issue may have been introduced to your project via some other packages' peer dependency.

If you have a .tsconfig file, Add following to compilerOptions:

"paths": {
"react": [ "./node_modules/@types/react" ]
}
Gaitho Bernard
  • 231
  • 2
  • 2
11

for react-native expo blank typescript template I had to add

package.json

  "resolutions": {
    "@types/react": "~17.0.47"
  },

then do yarn install again

tsconfig.json

{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "esModuleInterop": true,
    "strict": true,
    "jsx": "react"
  }
}
Norfeldt
  • 8,272
  • 23
  • 96
  • 152
  • For me it results in an exception being thrown on build: "Error: Failed to resolve entry for package "\node_modules\@types\react". The package may have incorrect main/module/exports specified in its package.json: No known conditions for "." specifier in "@types/react" package" – Notorious Zet Jul 07 '23 at 09:54
6

React 18 was released but appears to have broken multiple packages which had set their @types/react dependency to *: https://github.com/facebook/react/issues/24304

Tom Jin
  • 61
  • 1
0

I fixed this problem in my React Native, Typescript project by changing how I imported React. I changed my import statement to this:

import * as React from 'react';

And it worked!

ToluMals
  • 11
  • 2