Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use FormattedMessage inside an option tag in React 0.14?

Tags:

reactjs

I am trying to migrate my application from React 0.12 to React 0.14 and am having trouble with option elements that use react-intl FormattedMessage objects placed inside select tags.

Here is a sample JSX code:

<select>
<option value="value1"><FormattedMessage message={this.getIntlMessage('key1')}/></option>
<option value="value2"><FormattedMessage message={this.getIntlMessage('key2')}/></option>
</select>

This code works fine in React 0.12 and I see my translated option elements.

In react 0.14, I got this error:

Only strings and numbers are supported as <option> children.

I traced the message to this changeset in React that happened earlier this year:

https://github.com/facebook/react/pull/3847/files

How can I fix this issue? I can't be the only one trying to use internationalized option elements?

like image 302
Jason Keirstead Avatar asked Oct 30 '15 17:10

Jason Keirstead


4 Answers

This has always been an issue. React < 0.14 used to silently accept invalid DOM structure, in your case <span> elements inside <option> elements. The browser would then correct the DOM structure, and cause the virtual DOM managed by React to be out of sync with the real thing. You wouldn't see errors until you tried to re-render existing components instead of just re-mounting them.

react-intl V2.0.0, which will ship with support for React 0.14, allows you to use the Function-As-Child pattern to customize the way your Formatted* components render. See the "Function-As-Child Support" paragraph on this issue.

In your case, you would do:

<FormattedMessage message={this.getIntlMessage('key1')}>
  {(message) => <option value="value1">{message}</option>}
</FormattedMessage>
<FormattedMessage message={this.getIntlMessage('key2')}>
  {(message) => <option value="value2">{message}</option>}
</FormattedMessage>

I don't think there's a way to achieve this on the current stable version, 1.2.1.

like image 161
Alexandre Kirszenberg Avatar answered Nov 01 '22 00:11

Alexandre Kirszenberg


With react-intl v4.0.0 you can do this:

    <select
      className="content"
      name="type-enquiry"
      defaultValue="Type of Enquiry"
      onChange={handleChange}
      required
     >
      <option name="options" disabled hidden>
                Choose
      </option>
        <FormattedMessage id='contact.enquiry.a' key={'op' + '-' + 'a'}>
           {(message) => <option value='a'>{message}</option>}
        </FormattedMessage>
        <FormattedMessage id='contact.enquiry.b' key={'op' + '-' + 'b'}>
           {(message) => <option value='b'>{message}</option>}
        </FormattedMessage>
        <FormattedMessage id='contact.enquiry.c' key={'op' + '-' + 'c'}>
           {(message) => <option value='c'>{message}</option>}
        </FormattedMessage>
    </select>

example code

like image 39
kalwalt Avatar answered Oct 05 '22 03:10

kalwalt


I had the same problem and solved it via the injectIntl().

This function is used to wrap a component and will inject the intl context object created by the IntlProvider as a prop on the wrapped component. Using the HOC factory function alleviates the need for context to be a part of the public API.

That means all you have to do is wrap your component with the injectIntl function, like that:

import React, {Component, PropTypes} from 'react';
import {defineMessages, injectIntl, intlShape} from 'react-intl';

const messages = defineMessages({
    firstoption: {
        id: 'mycomponent.firstoption',
        defaultMessage: 'Coffee',
    },
    secondoption: {
        id: 'mycomponent.secondoption',
        defaultMessage: 'Tea',
    }
});

class MyComponent extends Component {
    render() {
        const {formatMessage} = this.props.intl;

        return (
          <div>
             <select>
                <option value="value1">{formatMessage(messages.firstoption)}</option>
                <option value="value2">{formatMessage(messages.secondoption)}</option>
             </select>
          </div>
        );
    }
}

MyComponent = {
    intl   : intlShape.isRequired
};

export default injectIntl(MyComponent)

Hope that helps...

like image 14
Alexander Avatar answered Nov 01 '22 01:11

Alexander


As a little bit better alternative to @Alexandre Kirszenberg answer, it's also possible to inject intl object into component and use formatMessage function directly,

import { injectIntl, intlShape, defineMessages, FormattedMessage } from 'react-intl';

const AddressForm = ({ intl, street, number, postalCode, city, onChange }) => {
  return (
    <form id="paymentAddress">
      // ...
      <fieldset className="form-group">
        <label htmlFor="country"><FormattedMessage { ...messages.country } />:</label>
        <div>
          <select name="country">
            <option value="DE">{intl.formatMessage(messages.de)}</option>
            <option value="UK">{intl.formatMessage(messages.uk)}</option>
            <option value="CH">{intl.formatMessage(messages.ch)}</option>
          </select>
        </div>
      </fieldset>
    </form>
  );
};

AddressForm.propTypes = {
  intl: intlShape.isRequired,
  // ...
}
like image 2
Alexander Beletsky Avatar answered Nov 01 '22 00:11

Alexander Beletsky