i18next / i18next-xhr-backend

[deprecated] can be replaced with i18next-http-backend
https://github.com/i18next/i18next-http-backend
MIT License
253 stars 75 forks source link

React Memorized Components Being Unnecessarily Re-rendered #329

Closed mwskwong closed 5 years ago

mwskwong commented 5 years ago

Describe the bug While accessing the t function in a memorized component (either memo or PureComponent), the component is being unnecessarily re-rendered. For me, it seems that this only happens if i18next-xhr-backend is used. In the codesandbox sample, 1 extra render is fired. While the worst I have met (in rare situation) is 7 extra renders.

Occurs in react-i18next version react-i18next@11.0.0

To Reproduce Just use useTranslation or withTranslation in a memorized component https://codesandbox.io/s/react-i18next-test-wzj08 https://codesandbox.io/s/react-i18next-test-z4jx0 (extended by @jamuhl)

Expected behaviour A memorized component should behave the same as a normal component in terms of utilizing react-i18next.

Additional context I have also posted this issue on react-i18next, since I'm not sure whether react-i18next or i18next-xhr-backend causes this. Original discussion: https://github.com/i18next/react-i18next/issues/710#issuecomment-546594002

@jamuhl Please read the console output for details (and forget whether the backend locates the translation.json or not). ONLY causes unnecessary re-renders when using i18next-xhr-backend

https://codesandbox.io/s/react-i18next-test-wzj08

If you believe it is easier for you guys to trace and maintain, I can open a new issue instead of continuing the discussion on this issue.

Wow...that is so weird - I extended your sample a little: https://codesandbox.io/s/react-i18next-test-z4jx0

(making sure no saveMissing, ... gets in the way, also adding a counter to the MemorizedButton to see it gets rendered to DOM only once - while called twice)

I moved the sample to local so I could add some more logs to the useTranslation:

// ...
  if (!ready && !useSuspense) return ret; // not yet loaded namespaces -> load them -> and trigger suspense

  throw new Promise(function(resolve) {
    console.warn(`${props.name} throw Promise (trigger Suspense)`);
    loadNamespaces(i18n, namespaces, function() {
      console.warn(`${props.name} set state (loaded namespace -> sets new t function)`);
      setT(getT());
      console.warn(`${props.name} resolves Promise (resume Suspense)`);
      resolve();
    });
  });

image

The SimpleButton gets triggered it's rerender by the setT. Immediately after the MemorizedButton also renders - honestly I have no idea why. Then Suspense get resolved by the SimpleButton - removing the fallbackUI and showing the real content again. Then setT gets called for the MemorizedButton - and it rerenders (calls the function internally) while the rendered result stays the memoized memorizedButton1 (counter says 2)...

Change the order of the 2 Buttons MemorizedButton before SimpleButton -> only one render as SimpleButton gives a shit about MemorizedButton gets setT called.

Move every Component in own Suspense -> both render only once

So...if this is related to react-i18next - feel free to provide a PR...as I don't know which magic triggers a rerender on the memorized component...I have no clue how to fix this...

To make it more clear - I got no idea what is triggering this render:

image

I just know it's not useTranslation (neither the loading done as it comes later nor the bound events)

jamuhl commented 5 years ago

Nothing todo with xhr-backend...not having one just does not trigger Suspense