i18next / react-i18next

Internationalization for react done right. Using the i18next i18n ecosystem.
https://react.i18next.com
MIT License
9.3k stars 1.03k forks source link

Error after upgrading: "Invariant Violation: Hooks can only be called inside the body of a function component." #761

Closed Dakkers closed 5 years ago

Dakkers commented 5 years ago

Describe the bug Invariant Violation: Hooks can only be called inside the body of a function component.

This error occurs for me after upgrading to the newest version of react-i18next. I'm using SSR for my app but this is occurring in the client-side rendering portion, so I don't think it's SSR specific. I compared my code against the razzle-ssr code and it's effectively the same, the only difference is my App.jsx is a class and not a function. (I tried changing my App to a function and I still got this error).

I am not using Hooks anywhere except for useSSR. I am only using withTranslation (because of decorators)

Occurs in react-i18next version

To Reproduce Not sure. Works fine in the Razzle example.

Expected behaviour I should be able to do... anything. My app is broken and I'm not sure why.

Screenshots If applicable, add screenshots or a GIF to help explain your problem. image

image

OS (please complete the following information):

Additional context

config:

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import XHR from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';

const options = {
  allLanguages: ['en'],
  fallbackLng: 'en',
  load: 'languageOnly',

  ns: [
    'about',
  ],
  defaultNS: 'translations',

  saveMissing: false,
  debug: false,

  interpolation: {
    escapeValue: false // not needed for react!!
  },
  wait: process && !process.release
};

// for browser use xhr backend to load translations and browser lng detector
if (process && !process.release) {
  i18n
    .use(XHR)
    // .use(Cache)
    .use(LanguageDetector);
}

i18n.use(initReactI18next);

// initialize if not already initialized
if (!i18n.isInitialized) {
  i18n.init(options);
}

export default i18n;

src/index.js (for client side) -- not all of the code but the relevant stuff.


const BaseApp = () => {
  useSSR(window.initialI18nStore, window.initialLanguage);
  return (
    <MobxProvider {...stores}>
      <BrowserRouter>
        <App
          isClientSideRendered={!window.IS_SSR}
          show404Page={window.show404Page || false}
          show500Page={window.show500Page || false}
          userData={window.userData}
        />
      </BrowserRouter>
    </MobxProvider>
  );
};

// It is important to keep the following in sync with `server/app.js`.

ReactDOM.hydrate(
  <BaseApp />,
  document.getElementById('root')
);
jamuhl commented 5 years ago

react and react-dom up-to-date >= 16.8.0

Dakkers commented 5 years ago

They are. useSSR doesn't throw any errors

jamuhl commented 5 years ago

i do not get this errors at all...so must be something in hydrate internals

Dakkers commented 5 years ago

I tried changing hydrate to render, same error. (also removed the SSR stuff when I changed it to render). I think I might know the issue but I'll double check and come back.

Dakkers commented 5 years ago

@jamuhl originally i thought i wasn't running my API, but I was and the same error is occurring.

jamuhl commented 5 years ago

Please provide a sample to reproduce on codesandbox...can't reproduce this without (sorry)

christopher-johnson commented 5 years ago

I can report a similar problem with v10. I do not believe that this is related to hydrate

My problem (I suspect) could be related to the useContext hook in this method. https://github.com/i18next/react-i18next/blob/0fafb4563a9c852792bd6851b45008d1315566f0/src/Trans.js#L133 In my app, I am importing another app (across the imperative boundary) that is only class components. I create my own i18n instance, and down the tree, they have use theirs (in a different context). The stack trace is attached. localhost-1551427723908.log

jamuhl commented 5 years ago

Trans component is a function: https://github.com/i18next/react-i18next/blob/master/src/Trans.js#L119

so there should no be such warning

christopher-johnson commented 5 years ago

in the stacktrace, you can see that useContext executes (not sure why yet). The caller (at z (http://localhost:3000/static/js/bundle.js:174709:42) is this method (which is Trans#133 transpiled)

    function z(e) {
      var t = (arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}).i18n,
          n = (A() ? Object(o.useContext)(R) : {}).i18n,
          r = t || n || j();
christopher-johnson commented 5 years ago

actually, stepping through the code, I realize now that this is from useTranslation not Trans. it looks like if getHasUsedI18nextProvider is true, then it executes useContext, which it will in my case, because there are two providers.

jamuhl commented 5 years ago

that changes nothing...useContext gets executed when you're using a provider -> still this usage of useContext is in a functional component and therefore should not give an error

jamuhl commented 5 years ago

Or are you using useTranslation in a class component?

christopher-johnson commented 5 years ago

the upstream app has just started using the withTranslation HOC on their class components, and it looks like this method in useTranslation is called from that.

jamuhl commented 5 years ago

Also the HOC wrappes using a functional component: https://github.com/i18next/react-i18next/blob/master/src/withTranslation.js#L6

So this is not the cause...

christopher-johnson commented 5 years ago

I have solved this. The problem is that upstream bundles react and react-dom. Discussed here: https://reactjs.org/warnings/invalid-hook-call-warning.html. (#3)

So, it appears that my specific error is unrelated to react-i18next

Adding this to the upstream webpack bundler is the fix:

  externals: {
    // Don't bundle react or react-dom
    react: {
      commonjs: "react",
      commonjs2: "react",
      amd: "React",
      root: "React"
    },
    "react-dom": {
      commonjs: "react-dom",
      commonjs2: "react-dom",
      amd: "ReactDOM",
      root: "ReactDOM"
    }
  },
Dakkers commented 5 years ago

I'm using create-react-app to bundle my client side code, so I can't imagine changing the externals in the client webpack would work. Additionally this problem is occurring in my client side code, not my server code.

jamuhl commented 5 years ago

@Dakkers Like said...rather sure it's an issue with your code...so please provide a sample to reproduce.

Dakkers commented 5 years ago

FOUND IT HOLY FUCKIN CANOLI

My project is a monorepo with this structure:

site, admin are create-react-app apps. They have the dep react-script. This installs react under the hood. The top level folder react has its own dependency of react which lib1, lib2 reference. However, this is a problem: https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react

The way I fixed it (temporarily) is by removing web/node_modules/react. I will need to reorganize my app.

Dakkers commented 5 years ago

@jamuhl thank you for your assistance, same to you @christopher-johnson .

jamuhl commented 5 years ago

If you like this module don’t forget to star this repo. Make a tweet, share the word or have a look at our https://locize.com to support the devs of this project -> there are many ways to help this project :pray:

Lapsec commented 5 years ago

I have solved this. The problem is that upstream bundles react and react-dom. Discussed here: https://reactjs.org/warnings/invalid-hook-call-warning.html. (#3)

So, it appears that my specific error is unrelated to react-i18next

Adding this to the upstream webpack bundler is the fix:

  externals: {
    // Don't bundle react or react-dom
    react: {
      commonjs: "react",
      commonjs2: "react",
      amd: "React",
      root: "React"
    },
    "react-dom": {
      commonjs: "react-dom",
      commonjs2: "react-dom",
      amd: "ReactDOM",
      root: "ReactDOM"
    }
  },

I solved the problem by this way