-
-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
High Performance impact when using a lot of HOCs #456
Comments
I was thinking of maybe just letting I18NextProvider rerender instead of having each HOC do it for itself, but that unfortunately means that any |
It looks like Lines 69 to 77 in 844e7dd
|
Thank you for the time you take to investigate that. Currently i got no ideal idea to solve this. Eventual reducing the number of used hocs would help (what we do is using hocs on page level components and from there pass the plus try changing bindI18n, bindStore for inner components -> but like you said might have the draw back of not rerender inner children deeper the tree using shouldComponentUpdate |
I had another thought today: Instead of each Especially if I'd like to avoid having to get rid of th HOCs, as they feel like a way for each of our components to translate themselves in isolation, as opposed to relying on passing |
hm...the |
It does get recreated but on a per-component basis. I was thinking of having a single version of the |
Hm, the provider does only provide the i18n instance. But i guess we might do some mode in the hoc to not use it's own So there could be one hoc as direct child of the provider using as is and passing down But that will remove the support of having a default namespace set and loaded on the hocs deeper down....idk |
Even something as simple as the following would be helpful. const CURRENT_TRANSLATIONS = {};
function translate() {} // stub
function checkForRenderUpdates() {} // stub
let CURRENT_TRANSLATE = translationId => translate(translationId)
function setTranslations(...translations) {
const newTranslations = {};
// check to see if translation or namespaces already loaded
// (or in the default namespace)
check()
for (let translationId in translations) {
// merge namespaces
}
CURRENT_TRANSLATE = generateTranslator(newTranslations);
checkForRenderUpdates();
return newTranslations
}
function getTranslate() {
return CURRENT_TRANSLATE;
}
function translate(translationId) {
return getTranslate()(translationId);
}
translateHOC(Component) {
render() {
<Component t={getTranslate()} />
}
}
export default { getTranslate, translate }; We've moved from At the moment I'm loading all translations in at the beginning as namespaces seem to have a huge performance overhead. However, when later specifying a namespace, 'orders', or 'prices', This prevents depreciation of older components and forces one to disable all The affected components can be seen in this profile block, which takes 1 second for 15 components, of which the translations have already been loaded into the default namespace: |
@tavurth the problem is home made -> sure it delays rendering when you're loading namespaces via xhr -> so you will need to decide which namespaces you want to have in a lazy load namespace -> eg. stuff you need only on special usecases -> everything else should be loaded on init - or after rendering of eg. dashboard. For your own solution -> do not merge namespaces into one -> push them into the resource store using the API available: https://www.i18next.com/overview/api#resource-handling There is also no difference in access speed: namespaces are just a nesting construct in the resource store -> lng.namespace.key.some.nested.structure.to.lookup Every call to |
Ah I see, so the proper depreciation workflow is as follows? languages.forEach(lng =>
loadNamespaces(lng).then(namespaces =>
namespaces.forEach(ns => i18next.addResources(lng, ns, namespaces[ns]))
)
); |
depends if that is something custom returning translations -> then i would guess yes |
Does using render props obviate the performance issue reported here? |
@jeznag @beheh i guess no: https://github.com/i18next/react-i18next/blob/master/src/I18n.js#L102 seems to be a problem enforcing rerender on each component nested inside. But like said - personally i never run into noticeable performance issues on our projects - and those are not small at all. But we do not have very deep component trees, mostly just |
@beheh an isolated test case for reproducing this behaviour would be great...as i already plan for including the new context api in upcoming version this would be awesome to assert we do not get that again. |
I've boostrapped a quick To see the issue, clone the repo, and run
|
@beheh is there a reason you can't use the wait option? |
@beheh just got an idea: the trigger for rerender is here: https://github.com/i18next/react-i18next/blob/master/src/I18n.js#L98 the problem eg. on load without wait is all the hocs call the loadNamespace which each will trigger a What you think if we check what event we got and eg. for ---edit--- Another option would be to just debounce setState there -> but that would add an additional delay. |
@beheh Beside setting wait to true setting those event listeners like needed would be a solution too: react: {
bindStore: false,
bindI18n: 'languageChanged'
}, i guess you do not rely on rerenders on Beside that i guess the simplest solution would be a debounced rerender on those events. |
@beheh guess i found an additional solution: Checking the ready state i saw that all those events were triggered before the "ready" render: https://github.com/i18next/react-i18next/blob/master/src/I18n.js#L62 so adding But this only works for initial rerenders - having a hoc that loads a different namespace (not yet loaded) all hocs would rerender too - so only option would be setting bindI18n as needed. So my suggestion would be creating a breaking version with:
bindStore: false,
bindI18n: 'languageChanged'
|
@jamuhl Thanks for your thoughts!
Regarding events, debouncing, I think react-i18next should try and avoid behaving too cleverly here. In the end there is some sort of state (the available languages and namespaces) stored in the i18next, that needs to be made available in a lot of places (every Trans component, every HOC...). The React solution here is to hoist this state up as a high as needed - for Translations usually toplevel - and then have it propagate downwards. The context API(s) are a very convenient way of doing that. The issue here is that this state is passed as a reference to the (the i18n instance) which makes figuring out whether stuff has changed quite tricky, and which led to the current state of listening to events and emitting I think one way this could be solved is by passing more primitive data structures around the place. As an extreme example, imagine you were passing an object containing the various namespaces around via the context API. Each component could then inspect this state object and decide whether any namespace it cares about has changed before comitting to a rerender. Today a very good improvement would be already to reliably detect just whether any change has occurred and only update one state (in the context provider) instead of states scattered throughout the component tree. And finally, thanks for the suggestion bailing out if ready is false! I'll try that out in the meantime. |
@beheh having slept over it i guess we have this situation of render triggered
The main problem is only in 2) while having rerender for individual events is ok the problem are the rerender triggered by So both:
The multiple call to So my final suggestion would be:
|
@beheh Update:
Please give feedback if the situation on your app is optimized with this changes. |
Thanks for the releases. Running a quick check still shows the same issue with 2.5 seconds of rerendering: Note how we see the two As we really want to get our page translated at this point we're considering to forgo the HOCs and switch to using the new Context API in our app to expose a t from |
@beheh saying "usable for larger apps" is a little to heavty like stated before:
We use react-i18next on multiple large projects. Anyway: using or set |
@beheh Why you're changed to use addResourceBundle and not let use the backend for loading? |
Sorry, I didn't mean to make that sound like react-i18next is unusable. We've been very happy with the flexibility so far! We're just running into a few snags with our specific configuration, and I'd like help to get all of those resolved. I haven't changed usage. This must be called by the backend. Is it possible it's called because we prepopulate I'll still try out |
Looks great, I think this issue is resolved for us! I'd like to dive in a bit more at some point to understand the events more closely and why this was happening. Either way thanks for the help! |
closing this for now - if still got some issues - let me know. |
In my app we've noticed a heavy overall performance regression after switching to i18next with react-i18next. In our configuration we're using client-side rendering with asynchronously supplied translation files and a key-based fallback (also using ICU).
After some investigating, it looks like on one page where we use a few nested HOCs, loading the last part of the translations results in a very bad behaviour from react-i18next:
Note how each of the HOCs separately reacts to the load completion, firing
onI18nChanged
which does asetState
call (usingnew Date()
to force a rerender).Unfortunately, each of these single
setState
calls seems to result in a full rerender of our application, as React schedules and then commits the work. While rerendering isn't super expensive if we do it dozens of times this adds up to a lot of wasted time (which we actually see in a few extra seconds on page load!).I think the main issue here is that each HOC (which wraps an
I18n
component around our component) registers itself with it's own callback that is executed separately and not batched in any way.I don't exactly know how this could be handled differently, but there must be a more efficient way. Possibly we could take a look at Redux/Flux and see how they commit state changes that happen in a lot of places at once?
The text was updated successfully, but these errors were encountered: