FormatJS illustrates the following example integration with React, using their React mixin (react-intl).
var i18n = {
locales: ["en-US"],
messages: {
SHORT: "{product} cost {price, number, usd} if ordered by {deadline, date, medium}",
LONG: "{product} cost {price, number, usd} (or {price, number, eur}) if ordered by {deadline, date, medium}"
}
};
React.renderComponent(
Container({
locales: i18n.locales,
messages: i18n.messages
}),
document.getElementById('example')
);
where Container uses the ReactIntlMixin mixin.
Any children of Container get the i18n data (locales & messages) for free (if they also use the ReactIntlMixin) thanks to react-intl's usage of React’s context.
I am experiencing difficulty integrating FormatJS into my React-Router-based project. The ridiculously naïve approach of attaching locales and messages as props to my Router fails to provide the necessary context.
Is my only option setting this data as props on my actual handlers?
FWIW I wrap my Routes in a top-level Route for this reason (and to handle a little bit of global render-dependent bootstrapping).
Thanks, @jbelcher. I was converging there, as well, so that gives me some confidence I’m not making a mistake here :)
I also do that.
the new API in 0.11 makes this simple:
Router.run(routes, function(Handler) {
React.render(<Handler locales={i18n.locales}/>, document.body);
});
@gaearon @rpflorence any idea how to update that <Handler> props to re-render the whole app – for example, when the user changes the language? How could I set the props for <Handler/> ?
If I call setProps within the root route's handler, it warns it's an anti pattern since it's a child of other components. (or what could be an alternative for switching the language without reloading the page?)
I asked about this more generally on the Reacr-Intl project and basically they intend you to reload the page to switch UI language
Alan
Le 20 déc. 2014 à 13:08, Giampaolo Bellavite [email protected] a écrit :
@gaearon @rpflorence any idea how to update that
props to re-render the whole app – for example, when the user changes the language? How could I set the props for ? If I call setProps within the root route's handler, it warns it's an anti pattern since it's a child of other components. (or what could be an alternative for switching the language without reloading the page?)
—
Reply to this email directly or view it on GitHub.
@gpbl
any idea how to update that
<Handler>props to re-render the whole app – for example, when the user changes the language? How could I set the props for<Handler/>?
Why not do this?
var CurrentHandler = null;
function renderApp() {
if (CurrentHandler) {
React.render(<CurrentHandler someProp={something} />, document.body);
}
}
Router.run(routes, function(Handler) {
CurrentHandler = Handler;
renderApp();
});
something.onChange(function () {
renderApp();
});
wow @gaearon awesome !! that was simple! :+1:
Yeah I saw @rpflorence write a very similar example in some other issue. Probably worth adding a section to the docs, since it's a very common pattern!
as for Version 1.0.0 beta3, we can do:
var DefaultMessages = require("./../i18n/en");
var App = React.createClass({
mixins: [IntlMixin, State],
contextTypes: {
router: React.PropTypes.func.isRequired
},
getDefaultProps: function(){
return {
locales:["en-CA"],
messages:DefaultMessages
};
},
render: function(){ ... }
});
Hi @DEllement , can you explain more of what you did to get IntlMixin to work with 1.0.0 beta3?
I can't seem to pass the {locales, formats} object for react-intl with the mixin at all in the router, works fine without using the router though.
Hi, basically, App is my main Application Component it hold my global state for the whole application. getDefaultProps is called the first time the Component is created which return an obj with 2 properties : locales & messages. As long as those 2 properties return the correct objects ( an array of string & a json object ).
Others in that thread was suggesting to pass those object as properties in the Handler rendering call.
It didn't seam possible in 1.0.0beta3 so i decided that it will do the same things to pass those props inside getDefaultProps of my main Application Component. Although it make your component tightly coupled with those properties i don't think its a real problem for now.
Also take note that your ReactComponent need the IntlMixin
Sorry for reply 6 days later and i hope it help somebody. :)
@DEllement Your way will cause a warning, Like below
owner-based and parent-based contexts differ (values: `undefined` vs `en`) for key (locales) while mounting FormattedNumber
It because IntlMixin requires a context from owner, but in this situation, your component's owner is React Router, But not your App component.
My way is add intl context part to react Router object, because it's your component's owner, In my project, it works perfectly, no warning, no tightly coupled, and your nested route will get the rignt intl context data, it will works as you expected too.
Here is my code. I think this is the best way to integrated react-intl and react-router, any idea?
import {Router as ReactRouter, Route} from 'react-router';
class Router extends ReactRouter {
static get childContextTypes(){
return Object.assign(super.childContextTypes, {
locales: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.array
]),
formats : React.PropTypes.object,
messages: React.PropTypes.object
});
}
getChildContext(){
var context = super.getChildContext() || {};
var intl = this.props.intl || {};
context.locales = intl.locales || ['zh-CN'];
context.formats = intl.formats || {};
context.messages= intl.messages|| {};
return context;
}
}
React.render((
<Router intl={{locales:...}} ...OTHER PROPS HERE>
... YOUR ROUTES HERE
</Router>
),document.body);
Sorry, I'm using 1.0.0 beta3, I have not tested it on 0.13.3. You could
upgrade 1.0.0 and give it a try:)
On Fri, Aug 21, 2015 at 10:35 PM, apreg [email protected] wrote:
@garbin https://github.com/garbin I tried your solution but it gives me Uncaught
TypeError: Super expression must either be null or a function, not undefined.
I'm using react-router ^0.13.3.—
Reply to this email directly or view it on GitHub
https://github.com/rackt/react-router/issues/484#issuecomment-133445950.
Yeah, I just did that. Sorry for my lame question. By the way after the upgrade I get Uncaught Error: Invariant Violation: Server-side <Router>s need location, branch, params, and components props. Try using Router.run to get all the props you need
var intlData = {
locales: ['de', 'en'],
messages: strings
};
React.render((
<Router intl={intlData} >
...``` Now I'm trying to figure out how to prepare those props.
Sorry, I only use react-router in client side..., so afraid I can't answer
this question. perhaps other peoples can give you answer :)
On Sat, Aug 22, 2015 at 2:07 AM, apreg [email protected] wrote:
Yeah, I just did that. Sorry for my lame question. By the way after the
upgrade I get Uncaught Error: Invariant Violation: Server-sides
need location, branch, params, and components props. Try using Router.run
to get all the props you needvar intlData = {
locales: ['de', 'en'],
messages: strings
};
React.render((
...``` —
Reply to this email directly or view it on GitHub
https://github.com/rackt/react-router/issues/484#issuecomment-133516022.
I use it on client side too. :)
Adding history prop was the key. So, it does not throw those exceptions anymore. Thx.
React.render((
<Router history={history} intl={intlData} >
<Route name="/" path="/" component={LoginScreen}>
<Route name="login" path="login" component={LoginScreen}/>
<Route name="settings" path="settings" component={SettingsScreen}/>
</Route>
</Router>
), document.body);```
@garbin could you share the part where you integrate the IntlMixin mixin into the extended Router class, and where you set this.props.intl?
I would like to test your code with these details. The purpose is to centralize the calls to get the locale messages & formats from the router component so that all child components can access react-intl from the router, instead of adding a mixin on each child component.
Thanks,
Yes, It's dead simple:). just two steps, take it.
1.create a Component through React.createClass(), Mixins only works here.
var BaseComponent = React.createClass({
mixins:[ReactIntl.IntlMixin, ...your other mixin],
...OTHER MEMBERS
});
2.All of your component extends from BaseComponent, It'll be work.
class YourComponent extends BaseComponent{
...
}
This is not perfect, but it works!
On Sat, Aug 29, 2015 at 2:17 AM, pbreah [email protected] wrote:
@garbin https://github.com/garbin could you share the part where you
integrate the IntlMixin mixin into the extended Router class, and where you
set this.props.intl?I would like to test your code with these details. The purpose is to
centralize the calls to get the locale messages & formats from the router
component so that all child components can access react-intl from the
router, instead of adding a mixin on each child component.Thanks,
—
Reply to this email directly or view it on GitHub
https://github.com/rackt/react-router/issues/484#issuecomment-135852034.
@garbin thanks for your quick response.
Merging these with the mixin feature and class inheritance is a good way to solve it, but we end up with two parent classes:
import React from 'react';
import { history } from 'react-router/lib/BrowserHistory';
import {Router as ReactRouter, Route} from 'react-router';
import AsyncProps from 'react-router/lib/experimental/AsyncProps';
import ReactIntl from 'react-intl';
var rootRoute = {
path: '/',
childRoutes: [
require('./routes/path1'),
require('./routes/path2')
],
component: require('./components/App')
};
// here is the mixin that adds IntlMixin
var BaseComponent = React.createClass({
mixins:[ReactIntl.IntlMixin]
});
// this similar to your example above
class Router extends ReactRouter {
static get childContextTypes(){
return Object.assign(super.childContextTypes, {
locales: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.array
]),
formats : React.PropTypes.object,
messages: React.PropTypes.object,
getmsg: React.PropTypes.func
});
}
getChildContext() {
var i18nLoader = require('i18n');
var context = super.getChildContext() || {};
context.locales = this.props.locales || ['en'];
context.formats = this.props.formats || {};
context.messages= this.props.messages|| {};
return context;
}
}
// Now need to inherit from Router and BaseComponent
// ran out of options as there is no multiple inheritance:
class BaseRouter extends Router {
// BaseComponent??
}
React.render((
<BaseRouter
children={rootRoute}
history={history}
createElement={AsyncProps.createElement}
/>
), document.getElementById('main-content'));
Any ideas how to approach this? how did you do your final implementation on your code with a centralized react-intl?
Thanks,
@pbreah The component I mentioned above is Component that will be used in <Route component={Component} /> , Not Router.
Here is what I did. I know it 's a different approach but maybe someone can get something out of it. There is this react-intl-es6 repo. I've extended Intl like this in my App component:
var locale = navigator.language.split('-')
locale = locale[0];
var strings = messages[locale] ? messages[locale] : messages['de']
strings = Object.assign(messages['de'], strings);
var intlData = {
locales: ['de', 'en'],
messages: strings
};
export default
class App extends Intl {
static propTypes = {
children: PropTypes.object
};
/*
static contextTypes = {
intl: PropTypes.object
};
*/
constructor() {
super( intlData.locales, intlData.messages )
}
render() {
return (
<span>
{React.addons.cloneWithProps(this.props.children)}
</span>
)
}
}
then in app.js
React.render((
<Router history={ new HashHistory()}>
<Route name="/" path="/" component={App}>
<Route name="login" path="login" component={LoginScreen}/>
<Route name="settings" path="settings" component={SettingsScreen}/>
</Route>
</Router>
), document.body);
and in LoginScreen.js
static contextTypes = {
intl: React.PropTypes.object
};
...
{this.context.intl.getMessage('login')}
@garbin and @apreg thanks for helping.
I finally got it working. I had to integrate it on the context (this.context) on the App component, then passed this to the children using a hack to propagate the "context" as custom props to the child components on the render method (for anyone that needs it):
// hack until React 0.14 is released.
var bodyComponent = React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, {
locales: this.props.locales,
messages: this.props.messages
});
}.bind(this));
Yeah, I guess that's what {React.addons.cloneWithProps(this.props.children)} is for in my code but mine is outdated, I know.
@apreg , I'm trying to get it working on my side too but I'm having an issue with react-intl-es6
I get the following error:
Uncaught TypeError: Super expression must either be null or a function, not undefined
When I want to do:
export default class Root extends Intl {
Also
import {Intl} from 'react-intl-es6'
console.log(Intl) -> gives me undefined
Do you have an idea?
I'm using react-router 1.0.0 b3
@jonaswindey You get undefined Intl probably because it is not exported. To solve this I added these few lines to node_modules/react-intl-es6/react-intl.js:
var _Intl = require('./Intl');
var _Intl2 = _interopRequireDefault(_Intl);
var _IntlApi = require('./IntlApi');
var _IntlApi2 = _interopRequireDefault(_IntlApi);
exports.Intl = _Intl2['default'];
exports.IntlApi = _IntlApi2['default'];
Hm, thanks for the tip. Seems a bit dirty though (and every npm install will overwrite this)
Strange that we can't create issues on react-intl-es6
Yeah, I agree. Maybe a pull request would be appropriate in this case but I'm too lazy right now :)
@jonaswindey thx :+1:
Hi All
I have found a good solution to be to transferring props to children as in
{React.cloneElement(this.props.children, {someExtraProp: something })}
see : https://github.com/rackt/react-router/blob/master/UPGRADE_GUIDE.md
so my code is something like
function renderApp(i18n) {
var React = require('react');
var ReactRouter = require('react-router');
var Router = ReactRouter.Router;
var App = React.createClass({
render: function () {
return (
<AppShell>
{React.cloneElement(this.props.children, {messages: i18n.messages , locales: i18n.locales})}
</AppShell>
);
}
});
var rootRoute = {
component: 'div',
childRoutes: [{
path: '/',
component: {App},
///etc
}]
};
React.render(
document.body
);
}
renderApp(////pass in locales/messages);
now I can access my required locales / messages in any child component of loaded view.
Tha should be
React.render(
document.body
);
I canot find a working example of implementing format.js, I browsed thru all the suggestions but I canot find a one that is working, could someone please give a working gist or something ?
+1
Wrap your Router in a container. This works with React v0.14 context. Please don't "+1" closed issues with unrelated comments unless you have something to add.
@taion got it thanks for the response!
To re-render the app for a different locale, a container is recommended (https://facebook.github.io/react/blog/2015/10/01/react-render-and-top-level-api.html). An example of the approach for React 0.14.*:
// Translated messages
var Messages = require('./components/messages');
// Add the locales we need
addLocaleData(en);
addLocaleData(zh);
window.AppState = {
container: document.getElementById("main"),
getLocale: function() {
return localStorage.getItem('locale') || 'en';
},
setLocale: function(lang) {
localStorage.setItem('locale', lang);
},
render: function() {
var locale = this.getLocale();
ReactDOM.render((
<IntlProvider locale={locale} messages={Messages[locale]}>
<Router history={browserHistory}>
...
</Router>
</IntlProvider>
), this.container);
},
unmount: function() {
ReactDOM.unmountComponentAtNode(this.container);
},
rerender: function() {
this.unmount();
this.render();
}
}
window.AppState.render();
Then the locale changer component can call:
window.AppState.setLocale('zh');
window.AppState.rerender();
@slimjim777 When i call rerender, i got the following warning. How can avoid it?
warning.js:44 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.
@KingWu You could try wrapping the call with a check for isMounted().
Most helpful comment
To re-render the app for a different locale, a container is recommended (https://facebook.github.io/react/blog/2015/10/01/react-render-and-top-level-api.html). An example of the approach for React 0.14.*:
Then the locale changer component can call: