Łukasz
Łukasz

Reputation: 133

Is there a way to return React Component as string instead of object?

I want to introduce a multilingual system. I have a function component that returns a translation of a specific element based on selected language (from Context API).

Everything works well until I put the translated element into the form (option value, placeholder, etc.) - then it appears as an [object object].

That's where 2 questions are born:

  1. Is it possible to return this component as a something like string, which HTML forms would accept?

  2. Is it possible to apply context consumer to pure JS function, so it does not return a React component, but a primitive value?

Translation component:

const Translation = ({ element }) => {
  let translation;

  return (
    <LanguageConsumer>
      {({ language }) => {
        switch (language) {
          case "pl":
            translation = plTranslation;
            break;

          case "en":
            translation = enTranslation;
            break;

          default:
            translation = enTranslation;
        }

        return translation[element];
      }}
    </LanguageConsumer>
  );
};

Example:

<option value="en">
  <Translation element="globalLanguageEnglish" />
</option>

Thanks in advance for your help.

Upvotes: 9

Views: 16888

Answers (3)

Moel
Moel

Reputation: 59

How about the approach to separate the translation logic from the rendering logic. Instead of returning a component that contains the translation, you can create a translation function or custom hook that returns the translated string.

Using a Translation Function or Custom Hook For example, you could create a custom hook that reads the context and returns a function for fetching translation strings:

import React, { useContext } from 'react';
import { LanguageContext } from './LanguageProvider';

const useTranslation = () => {
  const { language } = useContext(LanguageContext);
  const translations = language === 'pl' ? plTranslation : enTranslation;
  return (element) => translations[element];
};

export default useTranslation;

Then in your component you can do:

const MyComponent = () => {
  const t = useTranslation();

  return (
    <select>
      <option value="en">{t('globalLanguageEnglish')}</option>
      {/* other options */}
    </select>
  );
};

This way, you’re directly obtaining a string from your translation system rather than trying to render a component into a string.

Upvotes: 0

datchung
datchung

Reputation: 4652

I had a similar need where I had to support translated strings and wanted to return a "string" from a react component. I wanted to be able use the component with a typical function call and in the render section. Here is what I ended up doing.

This is the react component which returns the "string" value. Side note: I'm using redux to store a dictionary of strings but you can use something else.

import { useSelector } from 'react-redux';

export function StringValue({ stringId }) {
  const myStrings = useSelector((state) => state.strings);

  // Note: do your own error handling as required
  return myStrings[stringId];
}

Here is an example of how to use the component with a typical function call and as a component in render:

import { StringValue} from 'StringValue';
...

export function MyComponent(){
    ...

    return (
        <>
            {console.log('StringValue ', StringValue({ sId: 'myStringKey' }))}

            <StringValue sId="myStringKey" />
    ...

Upvotes: 0

xadm
xadm

Reputation: 8418

I can confirm it doesn't work - <option> html element content can be "Text, possibly with escaped characters (like &eacute;)."

React component can return only string, it's perfectly valid (and works as OP stated) ... but this use case is not supported.

Using components to just return a string is not a good choice. It can be ineffective when you need many translated strings in one component. React has to manage it as components, control props, render, make reconcilation etc. Many, following components (siblings) of the same type (only different props) should be key-ed.

Smarter solution: In this case pass an array of options (keys) as prop and use one component to render all translated <option> elements in a loop (return using fragment), f.e. like this:

// get some translation fn f.e. from context
return (
  <>
    {props.items.map( item => <option key="{item}" value="{item}">{translate(item)}</option> )}
  </>
)

Look how it's done at ready i18n solutions like i18next described here - pass function 't' and use it to access your translations.

Upvotes: 0

Related Questions