React-Intl How to use FormattedMessage in input placeholder - reactjs

I'm unsure how to get the values from
<FormattedMessage {...messages.placeholderIntlText} />
into a placeholder format like input:
<input placeholder={<FormattedMessage {...messages.placeholderIntlText} />} />
as it would return [Object object] in the actual placeholder. Is there a way to get the actual correct value?

The <Formatted... /> React components in react-intl are meant to be used in rendering scenarios and are not meant to be used in placeholders, alternate text, etc. They render HTML, not plain text, which is not useful in your scenario.
Instead, react-intl provides a lower level API for exactly this same reason. The rendering components themselves use this API under the hoods to format the values into HTML. Your scenario probably requires you to use the lower level formatMessage(...) API.
You should inject the intl object into your component by using the injectIntl HOC and then just format the message through the API.
Example:
import React from 'react';
import { injectIntl, intlShape } from 'react-intl';
const ChildComponent = ({ intl }) => {
const placeholder = intl.formatMessage({id: 'messageId'});
return(
<input placeholder={placeholder} />
);
}
ChildComponent.propTypes = {
intl: intlShape.isRequired
}
export default injectIntl(ChildComponent);
Please note that I'm using some ES6 features here, so adapt according to your setup.

You can use intl prop from injectIntl HoC
You can also provide function as child component:
<FormattedMessage {...messages.placeholderIntlText}>
{(msg) => (<input placeholder={msg} />)}
</FormattedMessage>

It's july 2019 and react-intl 3 beta is shipped with a useIntl hook to make these kind of translations easier:
import React from 'react';
import {useIntl, FormattedDate} from 'react-intl';
const FunctionComponent: React.FC<{date: number | Date}> = ({date}) => {
const intl = useIntl();
return (
<span title={intl.formatDate(date)}>
<FormattedDate value={date} />
</span>
);
};
export default FunctionComponent;
And then you can make custom hooks to use the methods provided by the API:
import { useIntl } from 'react-intl'
export function useFormatMessage(messageId) {
return useIntl().formatMessage({ id: messageId })
}

For Input placeholder for more details
<FormattedMessage id="yourid" defaultMessage="search">
{placeholder=>
<Input placeholder={placeholder}/>
}
</FormattedMessage>

As from React version >= 16.8, you can use useIntl hook :
import React from 'react';
import { IntlProvider, useIntl } from 'react-intl';
const FunctionComponent = () => {
const intl = useIntl();
const lang = "en";
const messages = {
en: {
'placeholderMessageId': 'placeholder in english',
},
fr: {
'placeholderMessageId': 'placeholder en fançais',
}
}
return (
<IntlProvider locale = {lang} messages = { messages[lang] } >
<input placeholder = { intl.formatMessage({ id: 'placeholderMessageId' })}/>
</IntlProvider >
);
};
export default FunctionComponent;

Based on the react intl wiki the implementation of an input box with translatable placeholder will look like:
import React from 'react';
import { injectIntl, intlShape, defineMessages } from 'react-intl';
const messages = defineMessages({
placeholder: {
id: 'myPlaceholderText',
defaultMessage: '{text} and static text',
},
});
const ComponentWithInput = ({ intl, placeholderText }) => {
return (
<input
placeholder={ intl.formatMessage(messages.placeholder, { text: placeholderText }) }
/>
);
};
ComponentWithInput.propTypes = {
intl: intlShape.isRequired
};
export default injectIntl(ComponentWithInput);
and the useage of it:
import ComponentWithInput from './component-with-input';
...
render() {
<ComponentWithInput placeholderText="foo" />
}
...
The id: 'myPlaceholderText', part is necessary to enable the babel-plugin-react-intl to collect the messages for translation.

I would like to suggest this solution :
import { useIntl } from "react-intl";
export default function MyComponent() {
const intl = useIntl();
return (
<input placeholder={intl.formatMessage({ id: "messageId" })} />
);
}

You are trying to render a React component named FormattedMessage into a placeholder tag which is expecting a string.
You should instead just create a function named FormattedMessage that returns a string into the placeholder.
function FormattedMessage(props) {
...
}
<input placeholder=`{$(FormattedMessage({...messages.placeholderIntlText})}` />

Consider this possibility.
The simplest solution
<IntlMessages id="category.name">
{text => (
<Input placeholder={text} />
)}
</IntlMessages>
OR

In my case I had the whole app in one file, so using export wouldn't work. This one uses the normal class structure so you can use the state and other functionality of React if needed.
class nameInputOrig extends React.Component {
render () {
const {formatMessage} = this.props.intl;
return (
<input type="text" placeholder={formatMessage({id:"placeholderIntlText"})} />
);
}
}
const nameInput = injectIntl(nameInputOrig);
Apply using the created constant:
class App extends React.Component {
render () {
<nameInput />
}
}

Starting from the #gazdagerg 's answer, I have adapted his code in order to:
having a new component that is a wrapper over an input
receives an ID of a string from locale conf
based on the ID, it returns the string in respect to the global locale setting
handling the situation when the string ID is not set (this caused exception and page to crash)
import React from 'react';
import { injectIntl, intlShape, defineMessages } from 'react-intl';
const InputWithPlaceholder = ({ intl, placeholder }) => {
const messages = defineMessages({
placeholder: {
id: placeholder,
defaultMessage: '',
},
});
if(messages.placeholder.id) {
return (
<input placeholder={ intl.formatMessage(messages.placeholder) } />
);
} else {
return (
<input/>
);
}
};
InputWithPlaceholder.propTypes = {
intl: intlShape.isRequired
};
export default injectIntl(InputWithPlaceholder);
You can use it in other file by:
import the new component
use it with the ID of the locale string as parameter
import InputWithIntlPlaceholder from 'your/path/to/component/InputWithIntlPlaceholder';
... more code here ...
<InputWithIntlPlaceholder placeholder="your.locale.string.id" />

Like this:
import React, {PropTypes} from 'react';
import { injectIntl, FormattedMessage } from 'react-intl';
 
/**
* {
* "hello": "Hello",
* "world": "World"
* }
*/
 
// pure function
const PureFunciton = injectIntl(({ intl }) => {
return (
<div>
<p>{intl.formatMessage({ id: 'hello' })}</p>
<p><FormattedMessage id="world" /></p>
</div>
)
});
 
// class Component
class componentName extends Component {
handleStr = () => {
// return 'Hello';
const { intl } = this.props;
return intl.formatMessage({ id: 'hello' })
}
render() {
return (
<div>
<p>{this.handleStr()}</p>
<p><FormattedMessage id="world" /></p>
</div>
);
}
}
 
export default injectIntl(connect(componentName));

Related

How to create a React component in TypeScript that lets me handle an event?

I would like to create a custom component in React using TypeScript that's essentially a combobox which has auto-complete/search functionality, connecting to its own remote store. What I would like to do is send an "onSelect" event so that I can receive the selected item where ever I'm using that component in my app.
Doing the auto-complete/search stuff with the remote store is easy, but the React component stuff has me stumped. I'm still learning both, so perhaps I'm trying to walk before I can crawl, but I don't want to start out creating a mess when I know that it should be possible to achieve this outcome which would be more elegant. I just need to find some sort of guide, but so far I haven't found one.
Here's what I want to achieve:
<MyCombobox onSelect={handleSelect} />
The handleSelect function would be used throughout my app where ever I need to use the MyCombobox component. The function needs to accept an argument, of course (which is what has me stumped at the moment, in TS).
One possible solution is as following
import * as React from "react";
import { render } from "react-dom";
interface MyComboProps {
// Here props from parent should be defined
}
interface MyComboState {
ValuesToShow: string[];
SearchValue: string;
}
class StringSearchMenu extends React.Component<MyComboProps, MyComboState> {
constructor(p: MyComboProps) {
super(p);
this.state = {
ValuesToShow: [],
SearchValue: ""
};
}
protected selectString(event: React.ChangeEvent<HTMLInputElement>): void {
let value = event.target.value;
if (value === "") this.setState({ ValuesToShow: [] });
else {
/* here you can put fetch logic. I use static array as example */
let possibleValues = ["Green", "Red", "Blue", "Yellow", "Black"];
this.setState({
ValuesToShow: possibleValues.filter(f => f.indexOf(value) > -1)
});
}
}
render() {
return (
<div>
Enter value to search {" "}
<input onChange={this.selectString.bind(this)} />
<div>
{this.state.ValuesToShow.map(v => (
<div>{v}</div>
))}
</div>
</div>
);
}
}
And working example is here
From all the googling I've done this morning, I've managed to cobble together something that works (from a plethora of sources):
App.tsx:
import React from 'react';
import './App.css';
import MyCombobox from './MyCombobox';
class App extends React.Component {
receiveSomething(something: string) {
alert('Something: ' + something);
}
render() {
return (
<div>
<MyCombobox receiveSomething={this.receiveSomething} defaultValue="qwerty" />
</div>
);
}
}
export default App;
MyCombobox.tsx:
import React from 'react';
export interface IMyCombobox {
defaultValue: string,
receiveSomething:(name:string) => void
}
class MyCombobox extends React.PureComponent<IMyCombobox, any> {
state = {
something: this.props.defaultValue
}
sendSomething() {
this.props.receiveSomething(this.state.something);
}
handleChange = (event: any) : void => {
this.setState({
something: event.target.value
});
}
render() {
return (
<div>
<input
type='text'
maxLength={20}
value={this.state.something}
onChange={this.handleChange} />
<input
type='button'
value='Send Something'
onClick={this.sendSomething.bind(this)} />
</div>
)
}
}
export default MyCombobox;

Render HOC(Component) without changing Component Name in JSX

I have two HOCs that add context to a component like so :
const withContextOne = Component => class extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
};
export default withContextOne;
Desired Result
I just want an syntactically concise way to wrap a component with this HOC so that it doesn't impact my JSX structure too much.
What I have tried
Exporting a component with the HOC attached export default withContextOne(withContextTwo(MyComponent)) This way is the most concise, but unfortunately it breaks my unit tests.
Trying to evaluate the HOC from within JSX like :
{ withContextOne(withContextTwo(<Component />)) }
This throws me an error saying
Functions are not valid as a React child. This may happen if you return a Component instead of < Component /> from render.
Creating a variable to store the HOC component in before rendering :
const HOC = withContextOne(Component)
Then simply rendering with <HOC {...props}/> etc. I don't like this method as it changes the name of the component within my JSX
You can set the displayName before returning the wrapped component.
const withContextOne = Component => {
class WithContextOneHOC extends React.Component {
render() {
return (
<ContextOne.Consumer>
{context => <Component {...this.props} one={context} /> }
</ContextOne.Consumer>
);
}
}
WithContextOneHOC.displayName = `WithContextOneHOC(${Component.displayName})`;
return WithContextOneHOC;
};
This will put <WithContextOneHOC(YourComponentHere)> in your React tree instead of just the generic React <Component> element.
You can use decorators to ease the syntactic pain of chained HOCs. I forget which specific babel plugin you need, it might (still) be babel-plugin-transform-decorators-legacy or could be babel-plugin-transform-decorators, depending on your version of babel.
For example:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
#withRouter
#resizeOnScroll
#injectIntl
#connect(s => s, (dispatch) => ({ dispatch }))
export default class FooBar extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
However, the caveat with decorators is that testing becomes a pain. You can't use decorators with const, so if you want to export a "clean" undecorated class you're out of luck. This is what I usually do now, purely for the sake of testing:
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { resizeOnScroll } from './Resize';
export class FooBarUndecorated extends Component {
handleOnClick = () => {
this.props.dispatch({ type: 'LOGIN' }).then(() => {
this.props.history.push('/login');
});
}
render() {
return <button onClick={}>
{this.props.formatMessage({ id: 'some-translation' })}
</button>
}
}
export default withRouter(
resizeOnScroll(
injectIntl(
connect(s => s, ({ dispatch }) => ({ dispatch }))(
FooBarUndecorated
)
)
)
);
// somewhere in my app
import FooBar from './FooBar';
// in a test so I don't have to use .dive().dive().dive().dive()
import { FooBarUndecorated } from 'src/components/FooBar';

How to retrieve a string in ReactIntl 2.0 without using FormattedMessage

Please correct me if I am wrong, FormattedMessage in ReactIntl returns a string wrapped by span tags. In ReactIntl 1.2, we have the option to use this.getIntlMessage('key') to get only the string part.
Here is my question: Is there an equivalent of that in ReactIntl 2.0? I am aware that the string can be obtained by using the Function-As-Child pattern in FormattedMessage as
<FormattedMessage id="placeholder">
{(formattedValue)=>(
<MyComponent ref="mycomponent" placeholder={formattedValue}/>
)}
</FormattedMessage>
However, it messes up the 'ref' in my component and I can't access to the component using this.refs.mycomponent any more.
There is a better to solve placeholder problem.
<FormattedMessage ...messages.placeholderIntlText>
{
(msg) => <input type="text" placeholder = {msg} />
}
</FormattedMessage>
You can easily return string using intl object provided by react-intl.
this is how you use intl object inside react class in much more easier way.
note: Render Component (main component) should wrap with IntlProvider
class MySubComponent extends React.Component{
{/*....*/}
render(){
return(
<div>
<input type="text" placeholder = {this.context.intl.formatMessage({id:"your_id", defaultMessage: "your default message"})}
</div>
)
}
}
MySubComponent.contextTypes ={
intl:React.PropTypes.object.isRequired
}
By defining contextTypes it will enable you to use intl object which is a context type prop. See react context for more details.
Ok, there is a work around for that. I can add ReactIntl as the context in the component like this:
contextTypes: {
intl: React.PropTypes.object.isRequired,
},
Then when trying to retrieve the string of the message and use it, for example in a placeholder, I can do this.
<MyComponent ref="mycomponent" placeholder={this.context.intl.messages.placeholder}/>
If you are using a functional component, then you can use useIntl() hook to get intl object and get the message as string from below code snippet.
import {IntlProvider, useIntl} from 'react-intl';
export function MyFunctionalComponent() {
const intl = useIntl();
return (
<div>
<p>{intl.formatMessage({id: "MyKey"})}</p>
</div>
)
}
Note: You Parent component should be wrapped around </IntlProvider> provider.
This is what I end up with. I added a central help function that retrieves the plain text.
import { useIntl } from 'react-intl';
export const GetTranslateText = (textId) => {
const intl = useIntl();
const plainText = intl.formatMessage({ id: textId });
return plainText;
};
Then it works fine to use to just retrieve the text in code or component
import { GetTranslateText } from '/utils/IntlHelpers';
.
.
<input type="text" placeholder={GetTranslateText('your.text.id')} />
.
.
I solved this problem using React render props.
I created an npm package that implements it: http://g14n.info/react-intl-inject/
It is a component like this
import { injectIntl } from 'react-intl'
export default function reactIntlInject ({ children, ...props }) {
if (typeof children === 'function') {
return (
children(props)
)
} else {
return null
}
}
And you can use it to wrap the components that for example has props you want to translate, for example
import React, { Component } from 'react'
// Import the package I created, available on npm with MIT license....
import InjectIntl from 'react-intl-inject'
// ...or copy the code above in a file in your project and import it, somethong like
// import InjectIntl from './path/to/my/render/prop/component'
class MyComponent extends Component {
render () {
return (
<InjectIntl>
{({ intl }) => (
<button
type='submit'
value={intl.formatMessage({ id: 'enter.submit' })}
/>
)}
</InjectIntl>
)
}
}
export default injectIntl(reactIntlInject)

How use data from Redux state?

I am new in Redux and React.
I changed data with action and they are changed !
I get to React component state. It is as follows:
How do I extract data from it and use it to render the component?
New
COMPONENT
import React from 'react';
import {connect} from 'react-redux';
import {filterChangeTab} from '../../actions/filters';
const FilterPage = React.createClass({
componentDidMount(){
const { dispatch, filterState } = this.props;
},
handleSelect: function (index, last) {
return this.props.dispatch(filterChangeTab(type[index]))
},
render() {
const {dispatch, filterState } = this.props;
return (
<div className="profile-page">
<Tabs onSelect={this.handleSelect} selectedIndex={1}></Tabs>
</div>
);
}
});
function select(state, props) {
let {
filterState
} = state;
return {
entries: filterState.entries
}
}
export default connect(select)(FilterPage);
Official documentation
The official documentation of redux is great and should guide you through your question. I would highly recommend to read through, as the following is just an excerpt.
Propagate data as props
I assume, that your store is successfully connected to your root component. Otherwise you should check the mentioned documentation again.
Also verify that you installed the react-redux bindings.
With this bindings, you could easily use the connect- API to connect your component to the redux store.
In your component you return an object with the entry entries:
return {
entries: filterState.entries
}
So you have to call this entry correctly in your render function:
import React from 'react';
import {connect} from 'react-redux';
import {filterChangeTab} from '../../actions/filters';
import Select from 'react-select';
const FilterPage = React.createClass({
componentDidMount(){
const { dispatch, filterState } = this.props;
},
handleSelect: function (index, last) {
let type = ["all", "categories", "people", "organization", "strength", "curators", "skills"];
return this.props.dispatch(filterChangeTab(type[index]))
},
render() {
//filterState.getState(); not work
// Use the correct names!
const {dispatch, entries} = this.props;
// Do whatever you want with this value
console.log(entries)
return (
<div className="profile-page">
Date range:
<Select
name="select"
value="All time"
clearable={false}
searchable={false}
options={options}
onChange={this.logChange}
/>
<Tabs onSelect={this.handleSelect} selectedIndex={1}>
</Tabs>
</div>
);
}
});
function select(state, props) {
const {filterState} = state;
const entries = filterState._root === undefined ? {} : filterState._root.entries;
return {
entries
}
}
export default connect(select)(FilterPage);

Decoupling React Components and Redux Connect

As seen here I am trying to decouple my app's components as much as I can and make them not aware of any storage or action creator.
The goal is to have them to manage their own state and call functions to emit a change. I have been told that you do this using props.
Considering
// Menu.jsx
import React from 'react'
import { className } from './menu.scss'
import Search from 'components/search'
class Menu extends React.Component {
render () {
return (
<div className={className}>
<a href='#/'>Home</a>
<a href='#/foo'>foo</a>
<a href='#/bar'>bar</a>
<Search />
</div>
)
}
}
And
// Search.jsx
import React from 'react'
import { className } from './search.scss'
class Search extends React.Component {
render () {
let { searchTerm, onSearch } = this.props
return (
<div className={`search ${className}`}>
<p>{searchTerm}</p>
<input
type='search'
onChange={(e) => onSearch(e.target.value)}
value={searchTerm}
/>
</div>
)
}
}
Search.propTypes = {
searchTerm: React.PropTypes.string,
onSearch: React.PropTypes.function
}
export default Search
And reading here I see a smart use of Provider and connect and my implementation would look something like this:
import { bindActionCreators, connect } from 'redux'
import actions from 'actions'
function mapStateToProps (state) {
return {
searchTerm: state.searchTerm
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
dispatchSearchAction: actions.search
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
Assuming I have a store handling searchTerm as part of the global state.
Problem is, where does this code belongs to? If I put it in Search.jsx I will couple actions with the component and more important to redux.
Am I supposed to have two different versions of my component, one decoupled and one connect()ed and have <Menu /> to use it? If yes what would my files tree look like? One file per component or a like a make-all-connected.js ?
In redux, exist a new kind of component that is called containers, this is the component that use connect(mapStateToProps, mapActionsToProps), to pass the state and actions to the current component.
All depends of the use of the component. For example, if you component Search only going to be use with the same state and action, You container could be the same that your component like this:
// Search.jsx
import { connect } from 'redux'
import actions from 'actions'
import React from 'react'
import { className } from './search.scss'
class Search extends React.Component {
render () {
let { searchTerm, onSearch } = this.props
return (
<div className={`search ${className}`}>
<p>{searchTerm}</p>
<input
type='search'
onChange={(e) => onSearch(e.target.value)}
value={searchTerm}
/>
</div>
)
}
}
Search.propTypes = {
searchTerm: React.PropTypes.string,
onSearch: React.PropTypes.function
}
function mapStateToProps ({searchTerm}) {
return {
searchTerm
};
}
const mapDispatchToProps = {
onSearch: actions.search
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
But if your plan is reuse this component in another containers and the searchTerm or the action are different on the global state. The best way is passing this properties through other containers, and keep the Search component pure. Like this:
// Container1.jsx
import { connect } from 'redux'
import actions from 'actions'
import React, { Component } from 'react'
class Container1 extends Component {
render() {
const { searchTerm, handleOnSearch } = this.props;
return (
<div>
<Search searchTerm={searchTerm} onSearch={handleOnSearch} />
</div>
)
}
}
function mapStateToProps ({interState: {searchTerm}}) {
return {
searchTerm
};
}
const mapDispatchToProps = {
handleOnSearch: actions.search
}
export default connect(mapStateToProps, mapDispatchToProps)(Container1)
// Container2.jsx
import { connect } from 'redux'
import otherActions from 'otheractions'
import React, { Component } from 'react'
class Container2 extends Component {
render() {
const { searchTerm, handleOnSearch } = this.props;
return (
<div>
<Search searchTerm={searchTerm} onSearch={handleOnSearch} />
</div>
)
}
}
function mapStateToProps ({otherState: {searchTerm}}) {
return {
searchTerm
};
}
const mapDispatchToProps = {
handleOnSearch: otherActions.search
}
export default connect(mapStateToProps, mapDispatchToProps)(Container2)
For more information, read the official docs about using redux with react.

Resources