简体   繁体   English

yarn workspaces 和 lerna 导致 Invalid Hook call

[英]yarn workspaces and lerna cause Invalid Hook call

I think this is a duplicate React problem我认为这是一个重复的 React 问题

Current behavior: In the container project, it works fine when I route to sub-app/foo .当前行为:container项目中,当我路由到sub-app/foo时它工作正常。 But when I route to sub-app I get the following error:但是当我路由到sub-app时,出现以下错误:

Uncaught Error: 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:
 You might have mismatching versions of React and the renderer (such as React DOM)
 You might be breaking the Rules of Hooks
 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.

Notice: sub-app/foo will get a class component.注意: sub-app/foo会得到一个 class 组件。 sub-app will get a React hooks component sub-app将获得一个 React hooks 组件

Expected behavior : The result I expect is that the subApp code will work fine in the container预期行为:我期望的结果是subApp代码将在container中正常工作

The Project Structure:项目结构:

/mono-repo
  packages
  - container
  - subApp
// /packages/container/src/App.js
import React from 'react'
import { BrowserRouter, Switch, Route, Link } from 'react-router-dom';
import About from './About';
import Header from './Header';
import Loadable from 'react-loadable'

const Logo = () => {
  return <div>LOGO</div>
}

const loadableComponent = (loader) => {
  return Loadable({
    loader: () =>
      loader().then(
        (res) => {
          return res
        },
        (e) => () => {
          console.log(e)
        }
      ),
    loading() {
      return (
        <div>loading</div>
      )
    }
  })
}

const loadSubApp = (subAppInfo) => {
  const { name, host } = subAppInfo
  return new Promise((resolve, reject)=> {
    fetch(`${host}/${name}/asset-manifest.json`)
      .then(res => res.json())
      .then(manifest => {
        const script = document.createElement('script');
        script.src = `${host}${manifest.files['main.js']}`;
        const timeout = setTimeout(()=>{
          console.error(`MicroApp ${name} timeout`);
          reject(new Error(`MicroApp ${name} timeout`));
        },20000)
        script.onload = () => {
          clearTimeout(timeout)
          const app = window[name]
          console.log({app, name})
          console.log(`MicroApp ${name} loaded success`);
          resolve(app)
        }
        script.onerror = (e) => {
          clearTimeout(timeout);
          console.error(`MicroApp ${name} loaded error`, e);
          reject(e)
        }
        document.body.appendChild(script);
      })
  })
}

const subLoader = (name) => async () => {
  const App = await loadSubApp({ name: 'subApp', host: process.env.REACT_APP_SUBAPP_HOST })
  console.log({App, window})
  return App[name]
}



const App = () => {
  return (
    <BrowserRouter>
      <React.Fragment>
        <Logo />
        <ul>
          <li>
          <Link to="/">Home</Link>
          </li>
          <li>
          <Link to="/header">header</Link>
          </li>
          <li>
          <Link to="/sub-app">subApp</Link>
          </li>
          <li>
          <Link to="/sub-app/foo">subApp foo</Link>
          </li>
          
        </ul>
        <Switch>
          <Route exact path="/" component={About} />
          <Route exact path="/header" render={() => <Header /> }/>
          <Route exact path="/sub-app/foo" render={()=> {
            const RenderSubApp = loadableComponent(subLoader('Foo'))
            return <RenderSubApp />
          }}/>
          <Route exact path="/sub-app" render={()=> {
            const RenderSubApp = loadableComponent(subLoader('Count'))
            return <RenderSubApp />
          }}/>
        </Switch>
      </React.Fragment>
    </BrowserRouter>
  )
}

// /packages/subApp/config-overrides.js
const {
  override,
  // addWebpackAlias
} = require("customize-cra");
// const path = require("path");

const dropConsole = () => {
  return (config) => {

    if (config.optimization.minimizer) {
      config.optimization.minimizer.forEach((minimizer) => {
        if (minimizer.constructor.name === "TerserPlugin") {
          minimizer.options.terserOptions.compress.drop_console = true;
        }
      });
    }
    return config;
  };
};

const optBuild= () => config => {
  config.optimization.runtimeChunk = false;
  config.optimization.splitChunks = {
    cacheGroups: {
      default: false,
    },
  }; 
  return config
}

const disableSourceMap = () => (config) => {
  if (process.env.NODE_ENV === "production") {
    config.devtool = false;
  }
  return config;
};

const customizeCraOverride = override(
  disableSourceMap(),
  dropConsole(),
  optBuild(),
  // addWebpackAlias({
  //   'react': path.resolve(__dirname, '../container/node_modules/react'),
  //   'react-dom': path.resolve(__dirname, '../container/node_modules/react-dom')
  // })
);

const webpack = (config, env) => {
  const webpackConfig = customizeCraOverride(config, env);

  return {
    ...webpackConfig,
    output: {
      ...webpackConfig.output,
      library: "subApp",
      libraryTarget: 'window',
    },
    // externals: {
    //   'react': 'react',
    //   'react-dom': 'react-dom'
    // },
  };
};

module.exports = {
  webpack,
};

The project link bug-demo项目链接bug-demo

those solution doesn't work for me这些解决方案对我不起作用

Finally solved this problem.终于解决了这个问题。

// /packages/subApp/config-overrides.js

 ...

const webpack = (config, env) => {
  const webpackConfig = customizeCraOverride(config, env);

  return {
    ...webpackConfig,
    output: {
      ...webpackConfig.output,
      library: "subApp",
      libraryTarget: 'window',
    },
    externals: {
       'react': 'React',
       'react-dom': 'ReactDOM'
    },
  };
};
module.exports = {
  webpack
};

// packages/container/src/index.js

...

export {
  React,
  ReactDOM
}
// packages/container/config-overrides.js

  ...

const webpack = (config, env) => {
  const webpackConfig = customizeCraOverride(config, env);
  return {
    ...webpackConfig,
    output: {
      ...webpackConfig.output,
      libraryTarget: 'umd',
    },
  }
};
module.exports = {
  webpack
};

I am not sure if this completely solves your problem but the error goes away and the routes work correctly.我不确定这是否能完全解决您的问题,但错误消失并且路线正常工作。 Nothing loads, I am pretty sure that I don't have the correct environment variables.没有任何加载,我很确定我没有正确的环境变量。 But, basically you just had some faulty router code in your /packages/container/src/App.js file.但是,基本上你的/packages/container/src/App.js文件中有一些错误的路由器代码。 I cloned the repository and this is what I changed it to:我克隆了存储库,这就是我将其更改为:

    <Switch>
      <Route exact path="/" component={About} />
      <Route exact path="/header" render={() => <Header /> }/>
      <Route exact path="/sub-app" render={()=> {
        const RenderSubApp = loadableComponent(subLoader('Count'))
        return <RenderSubApp />
      }}>
        <Route exact path="/foo" render={()=> {
          const RenderSubApp = loadableComponent(subLoader('Foo'))
          return <RenderSubApp />
        }}/>
      </Route>
    </Switch>

Notice how the /foo route becomes a child of the /sub-app route.注意/foo路由是如何成为/sub-app路由的子路由的。 Although the error is gone React still shows a warning message: Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored尽管错误消失了,React 仍然显示一条警告消息: Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored . Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored I am not exactly sure how lerna works, but there might be some workaround for this.我不确定 lerna 是如何工作的,但可能有一些解决方法。

Check out this section of the react-router docs.查看 react-router 文档的这一部分

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM