Render custom react component within HTML string from server

I have a HTML string that comes from the server, for example:

const myString = '<p>Here goes the text [[dropdown]] and it continues</p>`;

And I split this string into 3 parts so the result is the following:

const splitString = [
  '<p>Here goes the text ',
  ' and it continues</p>'

Then I process those 3 parts in order to replace the dropdown with a react component:

const processedArr = => {
  if (/* condition that checks if it's `[[dropdown]]` */) {
    return <Dropdown />;
  return item;

So after all, I get the processed array, which looks like this:

['<p>Here goes the text ', <Dropdown />, ' and it continues</p>']

When I render that, it renders the HTML as a text (obviously) with the Dropdown component (that renders properly) in between the text. The problem here is that I cannot use { __html: … } because it has to be used such as <div dangerouslySetInnerHTML={{ __html: … }} />. I cannot add <div> around the string because that would cut out the <p> tag.

I thought about splitting the parts into tags and then in some sort of loop doing something like:

React.createElement(tagName, null, firstTextPart, reactComponent, secondTextPart);

but that would require fairly complex logic because there could be multiple [[dropdown]]s within one <p> tag and there could be nested tags as well.

So I’m stuck right now. Maybe I’m looking at the problem from a very strange angle and this could be accomplished differently in React. I know that React community discourages rendering HTML from strings, but I cannot go around this, I always have to receive the text from the server.

The only stackoverflow question I found relevant was this one, however that supposes that content coming from backend has always the same structure so it cannot be used in my case where content can be anything.

After some more digging, I found this question and answer which seems to be kinda solving my problem. But it still feels a bit odd to use react-dom/server package with its renderToString method to translate my component into a string and then concatenate it. But I’ll give it a try and will post more info if it works and fits my needs.

So after playing with the code, I finally came to a “solution”. It’s not perfect, but I haven’t found any other way to accomplish my task.

I don’t process the splitString the way I did. The .map will look a bit different:

// Reset before `.map` and also set it up in your component's constructor.
this.dropdownComponents = [];

const processedArr = => {
  if (/* condition that checks if it's `[[dropdown]]` */) {
    const DROPDOWN_SELECTOR = `dropdown-${/* unique id here */}`;
      component: <Dropdown />,
      selector: DROPDOWN_SELECTOR
    return `<span id="${DROPDOWN_SELECTOR}"></span>`;
  return item;

Then for componentDidMount and componentDidUpdate, call the following method:

  _renderDropdowns() {
    this.dropdownComponents.forEach((dropdownComponent) => {
      const container = document.getElementById(dropdownComponent.selector);
      ReactDOM.render(dropdownComponent.component, container);

It will make sure that what’s within the span tag with a particular dropdown id will be replaced by the component. Having above method in componentDidMount and componentDidUpdate makes sure that when you pass any new props, the props will be updated. In my example I don’t pass any props, but in real-world example you’d normally pass props.

So after all, I didn’t have to use react-dom/server renderToString.