0

I have recently upgraded a React project to 18 and it's dependencies:

react-project package.json:

"dependencies": {
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
  "react-router-dom": "^6.3.0",
  "react-scripts": "5.0.1"
},
"devDependencies": {
  "@types/node": "^16.11.45",
  "@types/react": "^18.0.15",
  "@types/react-dom": "^18.0.6",
  "@types/react-router-dom": "^5.3.3",
  "react-app-alias-ex": "^2.1.0",
  "react-app-rewired": "^2.2.1",
  "typescript": "^4.7.4"
},

I have a shared react base library, which I have upgraded as well and I use it on this project:

react-shared package.json:

"dependencies": {
  "dotenv": "^16.0.1",
  "graphql": "^16.5.0",
  "react": "^18.2.0",
  "react-dom": "^18.2.0",
  "react-router-dom": "^6.3.0",
  "redux-thunk": "^2.4.1",
  "typesafe-actions": "^5.1.0"
},
"devDependencies": {
  "@types/node": "^18.0.6",
  "@types/react": "^18.0.15",
  "@types/react-dom": "^18.0.6",
  "@types/react-router-dom": "^5.3.3",
  "tailwindcss": "^3.0.22",
  "tsconfig-paths": "^4.0.0",
  "typescript": "^4.7.4"
}

I am getting errors on the browser's console when I run it, saying:

Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: {...}
    useOutlet hooks.tsx:214
    Outlet components.tsx:110
    {...}

I have created a private and a public outlet, using react-router-dom:

react-shared private.outlet.tsx:

export default class PrivateOutlet extends Component {
  public constructor(props: any) {
    super(props);
  }

  public render(): RenderResult {
    if (loggedIn) {
      return <Outlet />;
    }
    return <Navigate to="/login" replace />;
  }
}

react-shared public.outlet.tsx:

export default class PublicOutlet extends Component {
  public constructor(props: any) {
    super(props);
  }

  public render(): RenderResult {
    if (!loggedIn) {
      return <Outlet />;
    }
    return <Navigate to="/" replace />;
  }
}

And the application:

react-project application.tsx:

export default class ApplicationComponent extends Component {
  public constructor(props: any) {
    super(props);
  }

  public render(): RenderResult {
    return (
      <BrowserRouter>
        <Routes>
          <Route path='/' element={<PrivateOutlet />}>
            <Route index element={<MainPage />} />
          </Route>
          <Route path='/login' element={<PublicOutlet />}>
            <Route index element={<LoginPage />} />
          </Route>
        </Routes>
      </BrowserRouter>
    );
  }
}

Does anyone have any idea how I can fix this problem and why is it happening? I'm trying to avoid changing my class components library to functional... Has react finally scr*wed me?

If anyone needs any more details I'll add them, just ask.

EDIT #1:

Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem. react.development.js:209
    React (4)
        printWarning
        error
        resolveDispatcher
        useContext
    useOutlet hooks.tsx:214
    Outlet components.tsx:110
    React (8)
        renderWithHooks
        mountIndeterminateComponent
        beginWork
        beginWork$1
        performUnitOfWork
        workLoopSync
        renderRootSync
        performConcurrentWorkOnRoot
    workLoop scheduler.development.js:266
    flushWork scheduler.development.js:239
    performWorkUntilDeadline scheduler.development.js:533
    (Async: EventHandlerNonNull)
    js scheduler.development.js:571
    js scheduler.development.js:633
    factory react refresh:6
    Webpack (24)
        __webpack_require__
        fn
        js
        factory
        __webpack_require__
        fn
        js
        js
        factory
        __webpack_require__
        fn
        js
        factory
        __webpack_require__
        fn
        js
        factory
        __webpack_require__
        fn
        tsx
        factory
        __webpack_require__
        <anonymous>
        <anonymous>

EDIT #2:

Uncaught TypeError: dispatcher is null
    useContext React
    useOutlet hooks.tsx:214
    Outlet components.tsx:110
    React (11)
        renderWithHooks
        mountIndeterminateComponent
        beginWork
        callCallback
        invokeGuardedCallbackDev
        invokeGuardedCallback
        beginWork$1
        performUnitOfWork
        workLoopSync
        renderRootSync
        performConcurrentWorkOnRoot
    workLoop scheduler.development.js:266
    flushWork scheduler.development.js:239
    performWorkUntilDeadline scheduler.development.js:533
    js scheduler.development.js:571
    js scheduler.development.js:633
    factory react refresh:6
    Webpack (24)
        __webpack_require__
        fn
        js
        factory
        __webpack_require__
        fn
        js
        js
        factory
        __webpack_require__
        fn
        js
        factory
        __webpack_require__
        fn
        js
        factory
        __webpack_require__
        fn
        tsx
        factory
        __webpack_require__
        <anonymous>
        <anonymous>
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
his dudeness
  • 316
  • 3
  • 14
  • Not sure, from the code you've posted here, everything looks okay. Maybe try changing these components over to functional (at least temporarily) just to see if the error goes away or changes at all. – Henry Woody Jul 23 '22 at 18:18
  • Could you expand the Warning message to include all stack trace items? – Henry Woody Jul 23 '22 at 18:21
  • @HenryWoody I've added that error full trace under EDIT #1. – his dudeness Jul 23 '22 at 18:27
  • @HenryWoody I have just tested with both PrivateOutlet and PublicOutlet being functional components and it works. But I still would like to not cede ground to the functional revolution and keep them as class components (unless I have a full understanding of why I can't use class components) – his dudeness Jul 23 '22 at 18:32
  • There is also another error below that first one: "Uncaught TypeError: dispatcher is null". I've added it under EDIT #2. – his dudeness Jul 23 '22 at 18:35
  • 1
    Looked around a bit and wasn't able to find anything that would indicate why you'd be having this issue so I'm not sure, but maybe you'll be able to find something. I understand not wanted to switch without understanding why, but functional components with hooks are pretty nice and React and the community seem to be headed that way, so I would recommend using them (though I understand not wanting to migrate a large project over). – Henry Woody Jul 23 '22 at 18:59
  • @HenryWoody Yeah I always knew I'd have to migrate someday. Maybe this is my cue. Thanks though. – his dudeness Jul 23 '22 at 19:01
  • Class components for all practical intents and purposes are deprecated. They aren't incompatible, but it's not the direction React is heading. That said, I see nothing overtly incorrect in the code you've shared. Why do you have two package.json files though? Are you building React components in one project and trying to use them in another? Perhaps you have competing instances of React, i.e. bullet point 3 in the React hooks warning. – Drew Reese Jul 23 '22 at 21:49

1 Answers1

0

I've actually arrived at this solution through another question that spinned off from this one. Link here.

The problem was duplicate references due to me using a shared project and the react-app-alias-ex package. I fixed it by adding the duplicate reference as an alias at config-overrides.js:

const {aliasWebpack} = require('react-app-alias-ex');
const path = require('path');

const options = {
    alias: {
        '@my-shared': path.resolve('../PATH/TO/SHARED/src'),
        'react-router-dom': path.resolve('./node_modules/react-router-dom')
    }
};

module.exports = aliasWebpack(options);

There was another very weird problem that was causing this change to not work and I was able to make work by following these steps rigorously:

  1. Delete /node_modules, /dist and /package-lock.json.
  2. Run npm install.
  3. Run npm run react-app-rewired build (If you are not using react-app-rewired, running npm run react-scripts build might work but I have not tested it).
  4. Run application and test.
his dudeness
  • 316
  • 3
  • 14