Search code examples
javascriptreactjslocaleglobalization

How to get text element of the given react component in React.cloneElement?


Developers give me the headings of the table:

const CustomersTable = () => {

    var headers=<>
        <th>Name</th>
        <th>Age</th>
        <th>Another text</th>
    </>

    return <Table
               headers={headers}
           />
}

And this is the code of the Table component:

const Table = ({headers}) => {

    var clonedHeaders = React.Children
            .toArray(headers.props.children)
            .map(header => React.cloneElement(header, {
                className: "text-gray-900 py-3 font-light text-xs"
            }));

    return <table>
         <thead>
            <tr>
                {clonedHeaders}
            </tr>
         </thead>
    </table>
}

I can use React.cloneElement to add attributes to the elements I receive as props of my component.

However, I want to be able to change the text content of those received elements too.

For example, I want to call my locale translation function on table header elements, automatically. Right now, if developers want to make their tables multi-lingual, they should write this:

var headers = <>
    <th>{t('Name')}</th>
    <th>{t('Age')}</th>
    <th>{t('Other text')}</th>
</>

I want to centralize that t(text) function for all headers prop. Can I do that?


Solution

  • You can use the same technique on the child elements of the headers as you do on the headers themselves:

    const clonedHeaders = React.Children
            .toArray(headers.props.children)
            .map(header => React.cloneElement(header, {
                className: "text-gray-900 py-3 font-light text-xs",
                children: React.Children.toArray(header.props.children).map(child => {
                    return typeof child === "string" ? t(child) : child;
                })
            }));
    

    Live Example:

    const {useState} = React;
    
    function t(english) {
        // Just so we can see that it happens
        return english.toLocaleUpperCase();
    }
    
    const CustomersTable = () => {
    
        var headers=<React.Fragment>
            <th>Name</th>
            <th>Age</th>
            <th>Another text</th>
        </React.Fragment>;
    
        return <Table
                   headers={headers}
               />;
    };
    
    const Table = ({headers}) => {
    
        const clonedHeaders = React.Children
                .toArray(headers.props.children)
                .map(header => React.cloneElement(header, {
                    className: "text-gray-900 py-3 font-light text-xs",
                    children: React.Children.toArray(header.props.children).map(child => {
                        return typeof child === "string" ? t(child) : child;
                    })
                }));
    
        return <table>
             <thead>
                <tr>
                    {clonedHeaders}
                </tr>
             </thead>
        </table>;
    };
    
    ReactDOM.render(<CustomersTable />, document.getElementById("root"));
    <div id="root"></div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

    That example doesn't do any recursion, so it won't handle <th><span className="something">Name</span></th>. If you want to handle that, you'll have to write a recursive function to handle it, but it'll be along the same lines.