Changing locale and FormattedMessage not updated instantly
Closed this issue · 14 comments
Not sure if it is an expected behavior. Here is the code setup:
locales['en'] = {"basic.text": "En"}
locales['zh'] = {"basic.text": "中文"}
// App.js
<IntlProvider locale={this.state.locale} messages={locales[this.state.locale]}
// Some other wrappers
<FormattedMessage id="basic.text"/>
</IntlProvider>
When I change the locale through (after the component is mounted):
this.setState({
locale: "zh" // Or "en"
});
The text is not updated instantly. When I trigger some other related UI update, which result in the re-render of that FormattedMessage
, the update can be seen.
I have tried to call forceUpdate()
after changing state, but it doesn't work.
Can you create a reproducible example or failing test case?
In a word: I am closing this issue because there is not enough evidence supporting that this is a react-intl
problem.
--------------------------longer version (optional)---------------------
I tried to create a simple example to reproduce the bug but I failed. Today I spent more time looking into this problem. In my project, only some FormattedMessage*
didn't get updated instantly.
The difference between these "unfriendly" FormattedMessage
and those normal FormattedMessage
is that these bad FormattedMessage
are put in a component that is wrapped by connect
from react-redux
.
const ReduxHeader = connect(mapStateToProps)(Header);
I couldn't figure out the reason and I couldn't reproduce the bug. (Sadly)
But I fixed this issue by adding locale prop to the component:
Header.propTypes = {
...,
...,
locale: React.PropTypes.string // PURE BLACK MAGIC
}
And in the top level component I passed the locale to this component.
<Header locale={this.state.locale}/>
So it will force the header to re-render when locale is changed.
The only thing I am guessing it might be is the problem with shouldComponentUpdate
and context
:
facebook/react#2517
I've used another workaround for this. I call injectIntl
for all my redux connected components like this:
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(MyComponent));
Now all my FormattedMessage
components update correctly when switching locale
@djhi Good idea! This works because the wrapper component created in injectIntl
does not implement shouldComponentUpdate
.
This is probably worth adding somewhere in the docs on the Wiki. If you find a good place to do so, feel free to add it.
Thx, I will do that, maybe tomorrow
I've used another workaround for this. I call injectIntl for all my redux connected components like this:
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(MyComponent));
I found myself choosing between the workaround by @djhi and passing { pure: false }
to connect()
to bypass its shouldComponentUpdate
, as suggested in react-redux
Troubleshooting.
As far as I can tell, wrapping with injectIntl()
specifically passes intl-context
as a prop
to the wrapped component (https://github.com/yahoo/react-intl/blob/master/src/inject.js#L45), which means the wrapped component is still pure.
Hence, when the only context that needs to be passed is intl
, the above should be a cleaner fix than disabling shouldComponentUpdate
in connect()
, do you agree with that?
I spent a long time debugging this exact issue, and @djhi's solution saved me. Has this been added to the docs? If so, it's hard to find. (That or I'm just blind.)
I didn't quite like the idea of wrapping every single component in injectIntl
, but the advice on using key={ locale }
for IntlProvider
from here worked like a charm.
Wondering if it would help if FormattedMessage
would be changed to use injectIntl
internally instead of context?
EDIT: Probably not because injectIntl
also uses context internally
@dubov94 you don't need to wrap every component in injectIntl
just the those who implement some kind of shouldComponentUpdate
logic that blocks the subtree from rendering, like connect
from redux. And I think it is a way better approach since this just triggers the rerendering (changing existing dom elements) while the key change tears the whole dom down to build it new.
@TomiS no, it wouldn't. InjectIntl is using the context as well. You just need to get the context changes passt those Components which implement a shouldComponentUpdate
because if those don't rerender all components below wont either. Thats why they don't see the context change.
I didn't quite like the idea of wrapping every single component in
injectIntl
, but the advice on usingkey={ locale }
forIntlProvider
from here worked like a charm.
I understand that it could help in some specific scenarios, but take care. It is dangerous because it will discard all inner component state when the language changes.