reactjstypescriptreact-routerreact-router-domreact-app-rewired

React shared project throws "Invalid hook call" for Outlet implementation


I have recently extracted some base react code from one of my projects into a separated shared project:

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

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

And inside this shared project I have 2 Outlet implementations:

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

The problem I'm having is that when I use those Outlets on the target project I get the error:

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
    {...}

But if I put those Outlets directly in the target project and remove the shared projects references to the Outlets, it works perfectly. Does anyone understand why this is happening and how I can fix it?


Solution

  • 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.