2

I'm trying to build a bootstrap v3, ReactJS component using TypeScript.

The props are as following:

export interface ICheckboxProps {
    checked?: boolean;
    text: string | JSX.Element | (string | JSX.Element)[];
    onChange?: (checked: boolean) => void;
}

As it can be seen, text can be a string, an JSX.Element or a listing of both.

This is the render method:

render(): JSX.Element {
    const { text, checked = false } = this.props;

    if (text instanceof Array) {
        // what should I do with?
    }

    return (
        <div className="checkbox">
            <label>
                <input type="checkbox" defaultChecked={checked} />{text}
            </label>
        </div>
    );
}

In another component I use this one <Checkbox text={["I accept the ", <Oem.ExternalLink href={"https://some.link.here"} linkText={"terms & conditions"} />]} />

However, I get the following error/warning:

Each child in an array or iterator should have a unique "key" prop. Check the render method of Checkbox

I know that it is related to the text property but I have no idea how I should add a key (even if I know that text is an array).

2 Answers 2

2

You should iterate through the array and apply the key prop to any JSX element you find. You can do this with React.cloneElement().

if (text instanceof Array) {
  text = text.map((item, idx) => {
    if(typeof item === 'string') {
      // if it's a string, then just return it.
      return item;
    }
    // otherwise it's a ReactElement. Return a clone of it with the a key prop.
    return React.cloneElement(text[idx], {key: idx}) )
  });
}

More info about React.cloneElement() on the official docs:

Clone and return a new React element using element as the starting point. The resulting element will have the original element’s props with the new props merged in shallowly. New children will replace existing children. key and ref from the original element will be preserved.


Demo

const A = () => <p>bar</p>;
const B = () => <p>bat</p>;

let text = ["foo", <A />, "baz", <B />];

class App extends React.Component {

  render() {
    if (text instanceof Array) {
      text = text.map((item, idx) => {
        if(typeof item === 'string') {
          return item;
        }
        return React.cloneElement(text[idx], {key: idx})
      });
    }
    return (
      <div>
        {text}
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="app"></div>

Sign up to request clarification or add additional context in comments.

2 Comments

I had to change the inner if to typeof item === "string" as item instanceof String seems not to work. But at the end, this worked. Thanks.
@KingKerosin, yes sorry for that. I added a demo as well.
0

I think you'd better declare text as a single JSX element then wrap it in a <div> if you need a text + JSX :

<Checkbox text={<div> "I accept the " 
  <Oem.ExternalLink href={"https://some.link.here"} linkText={"terms & conditions"} />
  </div>}
/>

Edit : An even better approach is to use the recommanded way using a JSX child :

<Checkbox>
  <div>
    "I accept the "
    <Oem.ExternalLink 
       href={"https://some.link.here"}
       linkText={"terms & conditions"} 
    />
  </div>
</Checkbox>

Then in Checkbox component :

render(): JSX.Element {
    const { children, checked = false } = this.props;
    return (
        <div className="checkbox">
            <label>
                <input type="checkbox" defaultChecked={checked} />
                {children}
            </label>
        </div>
    );
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.