-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
How to use imperative API in a non-component script #416
Comments
You'd have to use dependency injection to pass the methods into your utility for it to use. Or drop to a lower-level using the It's possible that we'll create a proper FormatJS lib which contains much of what's in the Note though that one of the main reasons for |
I have the same issue. I have some action definitions, where I'd want them to have a property messages, which is an object of messages to show in the notifications component, if the action succeeds, fails, or is taking a long time to complete. A piece of action middleware, will translate those message properties into actions that add / remove them from a notification store. I understand your argument about making it a UI concern and get it as close to where it's displayed as possible, but the tight coupling to the UI makes it a bit awkward to work with for this particular case. Move the message definition to the notifications component and using identifiers for the messages would work, but that will result in a giant defineMessages object with all the notification messages, out side of their original context. This is about the only use case I can think of (I'm sure there are others :)), where it's not better to use the HoC and components. |
Why not pass a |
That's what we're doing now, and that means that we have the source text for the translation string in the component and not in the action where it belongs (in this particular case - it's a generic and I don't like having to duplicate the translation string into all places where the action is called). I have resorted to something like the below, in another case where I needed access to the context outside of a React Component: const intlProvider = new IntlProvider({ locale: localeId, messages }, {});
const { intl } = intlProvider.getChildContext(); But it's inconvenient, as you have to pass the localeId and messages through. I guess this is a problem that can't be solved without introducing a singleton or accepting that you have to pass a representation of the context around (at least the localeId and messages). What is your opinion about the above snippet? Is it kosher? |
Another use case is in our server, where we use a react-based email templating library. It requires the subject and other headers to be passed in as arguments, and uses JSX to compose the mail body. The subject thus needs to be passed using |
@gustavnikolaj I'm not sure I follow. Can you provide more example code so I can better understand the action use case and how you end up with duplicated strings? You can use I also don't understand why the email template needs to be rendered outside of an |
A Notification can be triggered from any connected component, that dispatch an action to the store, adding a notification to the list. If we have a single abstract action for adding a notification it might look like this: this.props.dispatch(addNotification({
message: this.props.intl.formatMessage(messages.notificationMessage),
}); What I would like to do is creating a notification wrapper that contains the message, so you could do this: this.props.dispatch(addFooNotification()); Where the action const messages = defineMessages({ /* ... */ });
function addFooNotification(locale, messagesForContext) {
const intlProvider = new IntlProvider({ locale, messages: messagesForContext }, {});
const { intl } = intlProvider.getChildContext();
return {
type: FOO,
message: intl.formatMessage(messages.notificationMessage)
};
} With regards to the emails, a crude example might look something like this: var messageBody = render(
<EmailMessage>
<h1><FormattedMessage defaultMessage='Foo' id='email.title' /></h1>
</EmailMessage>
);
mailerService.send({
headers: {
to: recipient,
subject: intl.formatMessage({ id: 'email.subject', defaultMessage: 'Foobar' })
},
body: messageBody
}); |
I hope this makes it more concrete :-) |
Yes, we also have a lot of messages in our actions and selectors. Surely, there should a way to use format utils outside of react components. |
@smashercosmo you can use the Intl APIs built into JavaScript, and core FormatJS libs. Why do you have lots of messages that you need to format in your actions? Can't you wait to format those messages when they end up in a React component tree? I also think using JSX to create React elements inside an action or store works well for describing rich text, localized messages; you could almost think of it as a thunk. |
@ericf Could you elaborate on how to do it as straightforward as possible? Without redoing all the work that react-intl is doing for you. Without, for example, reimplementing formatMessage function from react-intl. |
@smashercosmo You'd have to explain more about your setup, see my questions above. |
@ericf ok, here is an example: we have a notification system in our app. And these notifications are triggered in the action creators. So, for example, when user updates his profile, then USER_PROFILE_UPDATED_SUCCESS action is invoked and also NOTIFICATION_ADDED. NOTIFICATION_ADDED action has the actual message to show in its payload. And this text of course should be translated according to current locale. |
@smashercosmo so whatever is creating the |
@ericf well, the only problem with this approach is that third-party Notification component that we use accepts message prop only in the string form. |
@smashercosmo if it's rendering that prop as a child, then it should be switched to using |
@ericf yep, surely it should) but anyway, I have irrational feeling that passing React components as arguments to actions is semantically wrong, because actions should deal with pure data, because this data often goes to app state and app state should be fully serializable. |
@smashercosmo what data model would you use to represent rich text messages passed as a payload on an action? I'd argue that React elements is a great data structure for this, more capable than a string of HTML. |
Hi everyone Another use case of using imperative API is when you need to translate redux-form validation error messages. See validate function of this redux-form In the validate function, you return several potential error messages where you need to translate using imperative API since you're outside of a react component. How would you handle that ? Best regards |
@lauterry yep, we also faced that. We ended up in passing intl in validation function through own props. |
@ericf the thing is, that I don't want to pass rich text messages to actions. I want only simple strings to be be passed. And these strings could be in some cases saved to app state or sent to server for example. So that's why I don't understand, why can't we have formatMessage function, that will accept message object as a first param and locale as a second param? |
I ended up with the following workaround : In the validate function, instead of providing error string messages, you provide the id of the message
And you use
The problem with that solution is that What we need with this solution is to find a way to make |
@lauterry in that case I think, it should be something like
But I'm not sure that redux-form won't fail if you set error message as an object instead of a string |
What does your |
@lauterry this is the file where you messages are defined using defineMessage function. Of course you can define them right in the component file. |
@smashercosmo Can you explain me please ? |
@lauterry seems like this is not the right place for such kind of conversation) I answered you in twitter. |
I think the cleanest way might be to refactor IntlProvider to use some factory function to construct the @smashercosmo @Danita do you think that could work? |
Yes! I think that would be a good solution. I have been trying to do something like that myself but I couldn't wrap my head around the code. |
Hi,
I got "[React Intl] Missing locale data for locale: "zh-CN". Using default locale: "en" as fallback.". |
I have done it like that using Intl: https://www.npmjs.com/package/intl
|
Another use case using react-redux-toastr: I want to be able to call a state change outside of a component, using i18n, for example, within a saga or action:
EDITED: I was able to call redux-toastr and intl on componentDidUpdate(), where I can make changes on redux state, but after the notification I had to clean the error details from the redux state, so the toastr never gonna be triggered again. |
@Danita They have compatible APIs such as |
Hi @ericf, just looping back in here to see if this is still on the radar or the context solution is the recommended one? |
+1 |
No sure if people are still looking for a solution. But, here's how we solved this problem; 1) First we create a 'IntlGlobalProvider' component that inherits the context and props from the IntlProvider in our component tree; <ApolloProvider store={store} client={client}>
<IntlProvider>
<IntlGlobalProvider>
<Router history={history} children={routes} />
</IntlGlobalProvider>
</IntlProvider>
</ApolloProvider> 2) (inside IntlGlobalProvider.js) Then out of the context we get the intl functionality we want and expose this by a singleton. // NPM Modules
import { intlShape } from 'react-intl'
// ======================================================
// React intl passes the messages and format functions down the component
// tree using the 'context' scope. the injectIntl HOC basically takes these out
// of the context and injects them into the props of the component. To be able to
// import this translation functionality as a module anywhere (and not just inside react components),
// this function inherits props & context from its parent and exports a singleton that'll
// expose all that shizzle.
// ======================================================
var INTL
const IntlGlobalProvider = (props, context) => {
INTL = context.intl
return props.children
}
IntlGlobalProvider.contextTypes = {
intl: intlShape.isRequired
}
// ======================================================
// Class that exposes translations
// ======================================================
var instance
class IntlTranslator {
// Singleton
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
// ------------------------------------
// Formatting Functions
// ------------------------------------
formatMessage (message, values) {
return INTL.formatMessage(message, values)
}
}
export const intl = new IntlTranslator()
export default IntlGlobalProvider 3) Import it anywhere as a module import { defineMessages } from 'react-intl'
import { intl } from 'modules/core/IntlGlobalProvider'
const intlStrings = defineMessages({
translation: {
id: 'myid',
defaultMessage: 'Hey there',
description: 'someStuff'
},
intl.formatMessage(intlStrings.translation) |
In my case, I had some labels set in a separated js file outside react components, so I followed the same approach @smashercosmo mentioned before..
|
Im getting this error |
Here's a similar approach to the one posted by @SimonSomlai (note, uses typescript): import { Component } from 'react';
import { InjectedIntl, InjectedIntlProps, injectIntl } from 'react-intl';
/**
* Should only use this when not inside a React component (such as redux actions), see:
* https://github.com/yahoo/react-intl/issues/416
*/
export let intl: InjectedIntl = null;
class IntlGlobalProvider extends Component<InjectedIntlProps> {
constructor(props: any) {
super(props);
intl = this.props.intl;
}
public render() {
return this.props.children;
}
}
export default injectIntl(IntlGlobalProvider); Usage: import IntlGlobalProvider from '../core/globalIntl';
<IntlProvider>
<IntlGlobalProvider>
<YourAppRoot />
</IntlGlobalProvider>
</IntlProvider> Then anywhere you may need, such as in a redux action: import { intl } from '../core/globalIntl';
...
const messages = defineMessages({
errorTitle: {
id: 'errors.title',
defaultMessage: 'Error',
},
});
...
intl.formatMessage(messages.errorTitle) |
@SimonSomlai |
@phifa I had the same problem and the reason it is happening is that you are probably trying to execute the Example: import { intl } from '../core/globalIntl'
const myUtilMethod = () => {
setTimeout(() => {
const { formatMessage } = intl
formatMessage(messages.errorTitle)
}, 0)
} For me this solved the issue. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Issue was closed because of inactivity. If you think this is still a valid issue, please file a new issue with additional information. |
Issue was closed because of inactivity. If you think this is still a valid issue, please file a new issue with additional information. |
Just want to keep this link here if anyone needs. react-intl provides an api called https://github.com/formatjs/react-intl/blob/master/docs/API.md#createintl |
@rubayethossain Thx for the link. How would you use it ? Do you have an example ? |
const intl = createIntl({ locale: 'es', messages: messagesInSpanish })
// ...
intl.formatMessage({ id: 'seo.title' }) |
As I understand
injectIntl
provides the imperative API inside a React component class, but I couldn't find a way to use that API ouside a component, for example in a helper or a utility class. Is there any way to do so?The text was updated successfully, but these errors were encountered: