Currently I have the following code to expose react-intl to non-components, but it throws an error for intl as undefined.
I have created a separate component as 'CurrentLocale' and inject-intl to it. The exporting function t will use intl formatMessage from CurrentLocale context.
import React from 'react';
import {injectIntl} from 'react-intl';
import PropTypes from 'prop-types';
import { flow } from 'lodash';
class CurrentLocale extends React.Component {
constructor(props,context){
super();
console.log(context,props);
console.log(this.formatMessage);
const { intl } = this.context.intl;//this.props;
this.formatMessage = intl.formatMessage;
}
render() {
return false;
}
}
CurrentLocale.contextTypes={
intl:PropTypes.object,
};
injectIntl(CurrentLocale);
function intl() {
return new CurrentLocale();
}
function formatMessage(...args) {
return intl().formatMessage(...args);
}
const t = opts => {
const id = opts.id;
const type = opts.type;
const values = opts.values;
let t;
switch (type){
case 'message':
default:
t = formatMessage(id, values);
}
return t;
}
export default t;
t is called as in another plain javascript file as,
import t from './locale/t';
t( { type: 'message', id:'button.Next'});
Following is the error message.
Thanks in advance.
There's also another approach very simple I used for solving a similar problem: Provide access to the intl object for a non-component:
import { IntlProvider, addLocaleData } from 'react-intl';
import localeDataDE from 'react-intl/locale-data/de';
import localeDataEN from 'react-intl/locale-data/en';
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
addLocaleData([...localeDataEN, ...localeDataDE]);
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
const intlProvider = new IntlProvider({ locale, messages });
const { intl } = intlProvider.getChildContext();
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
It's working like a charm!
UPDATE for v3.x
After migration to react-intl 3.x
import { createIntl, createIntlCache } from 'react-intl'
import { formMessages } from '../../../store/i18n'; // I defined some messages here
import { Locale } from '../../../../utils'; //I set the locale fom here
const locale = Locale.setLocale(); //my own methods to retrieve locale
const messages = Locale.setMessages(); //getting messages from the json file.
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache();
const intl = createIntl({ locale, messages }, cache)
export const SCHEMA = {
salutation: {
label: intl.formatMessage(formMessages.salutationLabel),
errormessages: {
required: intl.formatMessage(formMessages.salutationError),
},
},
academic_title_code: {
label: intl.formatMessage(formMessages.academicTitleLabel),
},
};
There's a new way to do it pretty easily with createIntl, it returns an object that you can use outside React components. Here's an example from the documentation.
import {createIntl, createIntlCache, RawIntlProvider} from 'react-intl'
// This is optional but highly recommended
// since it prevents memory leak
const cache = createIntlCache()
const intl = createIntl({
locale: 'fr-FR',
messages: {}
}, cache)
// Call imperatively
intl.formatNumber(20)
// Pass it to IntlProvider
<RawIntlProvider value={intl}>{foo}</RawIntlProvider>
I personally store the intl object in Redux store so I can access it everywhere in my app.
This line: const { intl } = this.context.intl; should be const { intl } = this.context;
Here is a reference post of someone doing almost the exact same thing as you are: https://github.com/yahoo/react-intl/issues/983#issuecomment-342314143
In the above the author is creating essentially a singleton that is exported instead of creating a new instance each time like you have above. This might be something you want to consider as well.
There's also another way solving a similar problem to used react-intl formatMessage for non-components.
Create a LocaleStore.js store file.
import _formatMessage from "format-message";
export default class LocaleStore {
formatMessage = (id, values) => {
if (!(id in this.messages)) {
console.warn("Id not found in intl list: " + id);
return id;
}
return _formatMessage(this.messages[id], values);
};
}
import LocaleStore your CombinedStores.js
import LocaleStore from "./stores/LocaleStore";
import en from "./translations/en";
import de from "./translations/de";
import Global from "./stores/global"
const locale = new LocaleStore("en", {
en,
de
});
export default {
global:new Global(locale)
}
now you can use this in your GlobalStore.js
class GlobalStore {
constructor(locale) {
this.locale = locale;
}
formatMessage=(message_is,formatLanguage="en")=> {
return this.locale.formatMessage(message_id, formatLanguage);
}
}
react-intl decorates your React.Component with wrapped component which is injected internationalized message dynamically so that the locale data is able to be loaded dynamically.
import { injectIntl } from 'react-intl';
class MyComponent extends Component {
render() {
const intl = this.props;
const title = intl.formatMessage({ id: 'title' });
return (<div>{title}</div>);
}
};
export default injectIntl(MyComponent);
It can be applied only in view layer such as React.Component.
react-intl can't be used in Vanilla JS. For example,
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return 'Space is not allowed.';
}
}
};
One of alternative is react-intl-universal. It can be used not only in Component but also in Vanilla JS.
For example:
import intl from 'react-intl-universal';
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return intl.get('no_space');
}
}
};
See react-intl-universal online example
If you can accept to use a function component I prefer to use the useIntl hook
https://reactjs.org/docs/components-and-props.html#function-and-class-components
I can then get values like this:
import { useIntl } from "react-intl";
const intl = useIntl()
intl.formatMessage({ id: 'myId' }),
https://formatjs.io/docs/react-intl/api/#useintl-hook
Related
can we get current intl(locale) value outside react component?
const locales = {
locale: 'en',
messages: {},
};
import { IntlProvider, addLocaleData } from 'react-intl';
//below is for creating
const intlProvider = new IntlProvider(locales, {});
const { intl } = intlProvider.getChildContext();
//is there any code snippet to get current value from IntlProvider?
Yes you can use createIntl API: https://formatjs.io/docs/react-intl/api/#createintl
Im used to connecting React componts to Redux like this:
In index.js:
import { connect } from "react-redux";
import { doThing } from "store/actions";
import Component from "./component";
const mapState = (state) => {
const { foo } = state;
return {
foo
};
};
const mapDispatch = {
doThing,
};
export default connect(mapState, mapDispatch)(Component);
In component.js
import React from 'react';
const Component = ({foo, doThing}) => {
return (
// stuff
)
}
This has worked great but now I've moved over to TypeScript:
In index.tsx:
import { stateType } from "types";
import { connect } from "react-redux";
import { doThing } from "store/actions";
import Component from "./component";
const mapState = (state: stateType) => {
const { foo } = state;
return {
foo
};
};
const mapDispatch = {
doThing,
};
export default connect(mapState, mapDispatch)(Component);
In component.tsx
import { doThingArgTypes } from "types";
import React from 'react';
type Props = {
foo: string;
doThing: (arg0: doThingArgTypes) => void;
};
const Component: React.FC<Props> = ({foo, doThing}) => {
return (
// stuff
)
}
The code above works but it's annoying having to type out Props every time given that in some situations TypeScript can infer types. Also if I import a prop and dont use it I dont get any errors. Is there a smarter way to add types?
Unfortunately, I don't think there is another way to go around it. You have to provide the interfaces/type aliases for your React components, otherwise, the TypeScript compiler will throw an error stating that is similar to this: Property “XXX” does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes.
Sure, you can always take the easy way out and type your components as any, but that would defeat the one of the reasons of using TypeScript, as you will lose typechecking capabilities.
If you are working with connected components with Redux and other middlewares such as redux-observable, you may choose to use this package, typesafe-actions, which provides a bunch of typesafe utilities.
Spectrum uses specific pattern for modals which I would like to replicate with typescript, graphQL and graphQL-codegen generated hooks.
Now what I currently have:
GraphQL Client Schema
enum ModalType {
LOGIN,
SIGNUP,
}
type LoginModalProps{
loginVal: String!
}
type SignupModalProps{
signupVal: String!
}
extend type Query {
openedModal: OpenedModal!
}
union ModalProps = LoginModalProps | SignupModalProps
type OpenedModal {
modalType: ModalType!
modalProps: ModalProps!
}
GraphQL Query
export const GET_OPENED_MODAL = gql`
query OpenedModal{
openedModal #client{
modalType,
modalProps{
... on LoginModalProps {
loginVal
}
... on SignupModalProps {
signupVal
}
}
}
}
`;
Code above is used by graphql-codegen for generating type safe query hook used below.
import React from 'react';
import LoginModal from '../containers/modals/LoginModal';
import SignupModal from '../containers/modals/SignupModal';
import {
useOpenedModalQuery,
ModalType,
LoginModalProps,
SignupModalProps
} from '../../generated/graphql';
const ModalContainer: React.FC = () => {
const { data } = useOpenedModalQuery();
switch(data?.openedModal.modalType){
case ModalType.Login:
return <LoginModal {...data?.openedModal.modalProps as LoginModalProps}/>
case ModalType.Signup:
return <SignupModal {...data?.openedModal.modalProps as SignupModalProps}/>
default:
return null;
}
}
export default ModalContainer
Given modal declaration then looks as follows
import { LoginModalProps } from '../../../generated/graphql';
const LoginModal: React.FC<LoginModalProps> = ({
loginVal
}) => {
I am new in this typed and graphQL world and it feels to me that it could be done better - especially the switch case statement that I had to use instead of the original object.
Is there some way how to preserve coupling between ModalType and ModalPropsType?
And is the code still safe with the as casting on ModalProps?
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropType from 'prop-types';
import Counter from '../components/Counter';
import * as counterActions from '../store/modules/counter';
class CounterContainer extends Component {
handleIncrement = () => {
console.log('+');
const { CounterActions } = this.props;
CounterActions.increment();
}
handleDecrement = () => {
console.log('-');
const { CounterActions } = this.props;
CounterActions.decrement();
}
render() {
const { handleIncrement, handleDecrement } = this;
const { number } = this.props;
return (
<Counter
onIncrement={handleIncrement}
onDecrement={handleDecrement}
number={number}
/>
);
}
}
I am using ESLint airbnb.
When I write the above code
ESLint
raise Error an 'CounterActions' is missing in props validation. And 'number' is missing in props validation
so i add
CounterContainer.propTypes = {
number: PropType.number,
CounterActions: PropType.func,
};
propType "number" is not required, but has no corresponding defaultProps declaration.
propType "CounterActions" is not required, but has no corresponding defaultProps declaration.
I do not know how to change it from now on.
I'm trying to apply ESLint while following the tutorial.
How do I change it?
It's asking for you to set defaultProps since they are both optional. Since it looks like they're both required for this component, set them to isRequired:
CounterContainer.propTypes = {
number: PropType.number.isRequired,
CounterActions: PropType.func.isRequired,
};
This will bypass the react/require-default-props rule for those props.
Currently working with Redux and was wondering if there was a way to require in multiple modules into a single file, which is then exported again as a single module?
For example, in my actions/bookmark.js I group all actions related to bookmarks accordingly:
module.exports = {
fetchBookmarkList: () => {
return {
type: 'FETCH_LIST'
}
},
fetchBookmark: () => {
return {
type: 'FETCH_BOOKMARK'
}
}
}
Then in my actions/index.js file, I require in all groups of actions (which will include bookmark actions as well as others). Then I would like to export the entire file as a single module.
Schematically I had something like this in mind (obviously this code does not work):
actions/index.js:
module.exports = {
require('./bookmark');
require('./tags');
}
The reason that I want to do this is so that I only have to import a single action file that contains all my actions (i.e. the actions/index.js file):
Example component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
class BookmarkList extends Component {
constructor(props) {
super(props);
this.props.fetchBookmarkList();
}
render() {
return (
<div></div>
);
}
}
export default connect(null, actions)(BookmarkList);
I see that you es6 modules syntax in components then, why not in your redux files?
export const fetchBookmarkList = () => {
return {
type: 'FETCH_LIST'
}
};
export const fetchBookmark = () => {
return {
type: 'FETCH_BOOKMARK'
}
}
Based on this reference I think it could be possible to re-export everything like this:
export * from './bookmark'
export * from './tags;
But haven't tried it and I could be wrong.
I would NOT recommend you to pass all the actions to mapDispatchToProps, only the actions needed by your component
But you could just use in actions/index.js:
Using the spread operator:
module.exports = {
...require('./bookmark');
...require('./tags');
}
And in ES6 syntax:
actions/index.js:
export * from './bookmark'
export * from './tags'