2
votes

I have a block of HTML content that is being pulled in via a graphql query. Within this text string, I have 'variables' in the form %variable%. I need to replace the variables with dynamically generated content. The easiest way would be to have the variable replaced with the output from a React component. The reason for wanting to use a Component in this case, is because I need to get data from another source to replace the variable, and the data will change based on different inputs, so I can't just replace string for string.

If I do content.replace("%variable%,<Component />) I get:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel lobortis massa. Fusce ac enim ullamcorper, vulputate diam ut, consequat diam. Duis ante odio, suscipit a rhoncus eget, sollicitudin vel nibh. Proin ut urna sed diam dictum commodo sed nec felis. In hac habitasse platea dictumst. Vivamus varius nulla mi, [Object object] convallis lorem aliquam ac. Quisque congue nibh sit amet nisl ultrices gravida. Integer ac lobortis nibh. Donec interdum placerat nibh, eget pharetra tellus rutrum nec. In varius arcu eu luctus posuere. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean porttitor orci et eros auctor, at porta dui bibendum. Donec sed consequat felis.

So, %variable is being replaced by [Object object] - not what I was hoping...

I've looked at some other responses to similar queries, but can't really see a solution that would apply in this instance.

Is this possible?

2
replace function returns string. That's why it prints [Object object]. How to do this? Interesting question :)Mosh Feu
> I've looked at some other responses to similar queriesSorcererApprentice

2 Answers

1
votes

You can use a combination of ReactDOMServer.renderToStaticMarkup and dangerouslySetInnerHTML to tackle your situation.

working code demo

---Edited based on comment--- For multiple variable replacements, maintain a mapping of variables & components as shown in the code snippet as well as the demo link

Code snippet

const Comp1 = () => (
  <div>
    dynamic component 1 <br />
  </div>
);
const Comp2 = () => (
  <div>
    dynamic component 2 <br />
  </div>
);
const Comp3 = () => (
  <div>
    dynamic component 3 <br />
  </div>
);

const variableToCompMapping = {
  "%variable1%": Comp1,
  "%variable2%": Comp2,
  "%variable3%": Comp3
};

export default function App() {
  const [content, setContent] = useState("");

  useEffect(() => {
    setContent(`
    lorem %variable1% ipsum 
    lorem %variable2% ipsum 
    lorem %variable3% ipsum 
    `);
  }, []);

  const renderContent = () => {
    let result = content;
    for (let dynamicVar in variableToCompMapping) {
      const compAsHtml = ReactDOMServer.renderToStaticMarkup(
        variableToCompMapping[dynamicVar]()
      );
      result = result.replace(dynamicVar, compAsHtml);
    }
    return result;
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <div
        className="content"
        dangerouslySetInnerHTML={{
          __html: renderContent()
        }}
      />
    </div>
  );
}

1
votes

Here is a possible approach. split the content by the template %variable%. Iterate over the outcome, use map to render each piece. For each iteration, render also <Component />.

Like this:

import React from "react";
import "./styles.css";

const getContent = () =>
  "Lorem ipsum dolor sit amet %variable% Proin ut urna sed diam %variable% per inceptos himenaeos";

const Component = () => (
  <span style={{ color: "red" }}>I'm actually Component</span>
);

export default function App() {
  const [content, setContent] = React.useState([]);
  React.useEffect(() => {
    setContent(getContent().split("%variable%"));
  }, []);
  return (
    <div className="App">
      {content.map((child, i) => (
        <>
          {child}
          {i < content.length && <Component />}
        </>
      ))}
    </div>
  );
}

Update

In order to support different variable, use split with regex instead of plain string. The second part is to hold a mapper between the variable name and the equivalent Component and in render time check if the current item is a variable (startsWith('%')) render the equivalent Component, otherwise render the item (which is the next piece from the original string)

import React from "react";
import "./styles.css";

const getContent = () =>
  "Lorem ipsum dolor sit amet %variable% Proin ut urna sed diam %variable1% per inceptos himenaeos";

const Component = () => (
  <span style={{ color: "red" }}>I'm actually Component </span>
);

const Component1 = () => (
  <span style={{ color: "green" }}>I'm actually Component </span>
);

const mapper = {
  "%variable%": <Component />,
  "%variable1%": <Component1 />
};

export default function App() {
  const [content, setContent] = React.useState([]);
  React.useEffect(() => {
    setContent(getContent().split(/(%.*?% )/g));
  }, []);
  return (
    <div className="App">
      {content.map(child => (
        <>{child.startsWith("%") ? mapper[child.trim()] : child}</>
      ))}
    </div>
  );
}

https://codesandbox.io/s/gracious-panini-yylug?file=/src/App.js