skip to Main Content

Some solutions use dangerouslySetInnerHtml, but it only allows HTML parsing. I am trying to insert a combination of HTML and React components with dynamic variable, for example the code below has a string that has some text, HTML, a MathJax component, and some variables. All these in a string that I want the Parent React Component to parse.

import { useState } from "react";
import MathJax from "react-mathjax";
import { lhospital } from "./equations/equations";

function App() {
    const [text, setText] = useState(`<i>count</i>`);
    <MathJax.Provider>
        <div dangerouslySetInnerHTML: {{__html: `<strong>L'hospital</strong>: <MathJax.Node formula={lhospital} />`}} />
    </MathJax.Provider>
}
export default App

2

Answers


  1. Instead of Using above approach, I’ll implement this bellow

    const component = useMemo(() => {
      if(lhospital) {
        return (
          <div style={{display: 'inline-block}}>
            <strong>L'hospital :</strong>
            <MathJax.Node formula={lhospital} />
          </div>
       )
     }else {
       return(<></>);
     }
    }, [lhospital])
    
    return (
          <MathJax.Provider>
            {component}
          </MathJax.Provider>
        );
    
    Login or Signup to reply.
  2. We tend to think of JavaScript as a dynamic, interpreted language — which it is — but if you’re using any of the standard tooling to bundle your React app (as you absolutely should be, and do seem to be), then there’s also a static compilation step, and that step is very likely to rename any local variables, React components, and so on, and to remove any that aren’t used.

    This means that what you describe is theoretically impossible: at runtime, in the browser, when you go to parse the JSX string that you’ve stored, anything like <MyComponent /> or {myVariable} in that string is liable to be meaningless.

    But "theoretically impossible" really just means "impossible in the general case". If there are specific components that you’re interested in (e.g. MathJax.Node) and specific variables that you’re interested in (e.g. lhospital), then you can write code that specifically handles those. The code that you write will refer to those components and variables in a way that the compilation step will see and maintain.

    But you wouldn’t want to write a full JSX parser; that’s just not a reasonable amount of work. For one thing, JSX can embed arbitrary JavaScript expressions (inside {...}), which in turn can contain JSX, so a full JSX parser would have to support things like

    <>
      {
        foo
          ? <Bar onClick={event => onClickHandler(foo + 12, event)} />
          : null
      }
    </>
    

    and then, once you’ve parsed that, you’d need your own logic to essentially implement all of JavaScript inside JavaScript.

    This is possible — it’s essentially what @babel/standalone does — but it’s a huge amount of work, and so many things can go wrong. I can’t imagine you want to be in the business of doing that.

    Instead, I think you should, first, think about whether you really need this kind of dynamically-stored code. (You might want to ask a new question about your underlying goal, to see if anyone can suggest a better way to achieve that goal.)

    If you decide that you really do need this kind of dynamically-stored code, then you should figure out the specific things you want to support, and then come up with a specific way to represent those things, either as a well-defined subset of JSX or using your own format. One option might be to represent everything in JSON, which (downside) would be more verbose but (upside) makes parsing very simple.

    For example, if you want to support the equivalent of:

    <strong>L'hospital</strong>: <MathJax.Node formula={lhospital} />
    

    then a JSON representation might be:

    [
      {"element":{"type":"strong","children":["L'hospital"]}},
      ": ",
      {"element":{"type":"MathJax.Node","props":{"formula":{"variable": "lhospital"}}}}
    ]
    

    You’d then have a function that takes three arguments:

    • the object represented by the above JSON (obtained via JSON.parse)
    • a mapping from variable-names to values
    • a mapping from React component-names to React component classes/functions

    and performs the requisite substitutions, returning a React element as the result. This function would use React.createElement to create the actual React elements (both for HTML elements and for React components).

    That function could look like this:

    function performSubstitutions(data, variables, components, index) {
      if (typeof(data) !== 'object') {
        // Note: you may want to support some syntax for substituting variables
        // inside strings, firstly for the obvious reason (as a way to support
        // strings that contain stuff before or after a variable), and secondly as a
        // way to allow variable substitutions in property-names and other object
        // keys. This implementation, however, doesn't do that.
    
        return data;
      }
    
      if (data instanceof Array) {
        return data.map(
          (element, index) =>
            performSubstitutions(element, variables, components, index)
        );
      }
    
      const keysStr = Object.keys(data).join(', ');
    
      if (keysStr === 'element') {
        // { "element": { "type": TYPE, "props": PROPS, "children": CHILDREN } }
    
        const typeStr = data.element?.type;
    
        if (typeof(typeStr) !== 'string') {
          throw new Error('Element type must be a string');
        }
    
        const type =
          typeStr.toLowerCase() === typeStr
            ? typeStr                // presumably an HTML element
            : components[typeStr];
    
        if (! type) {
          throw new Error(`Unsupported type [${typeStr}]`);
        }
    
        const props = {};
        for (const [key, value] of Object.entries(data.element.props ?? {})) {
          props[key] = performSubstitutions(value, variables, components);
        }
        props.key ??= index;
    
        const children = performSubstitutions(
          data.element.children ?? [],
          variables,
          components);
    
        return React.createElement(type, props, children);
      }
    
      if (keysStr === 'raw') {
        // { "raw": RAW_VALUE } -- bypass all substitutions
    
        return data.raw;
      }
    
      if (keysStr === 'variable') {
        // { "variable": VAR_NAME }
    
        if (typeof(data.variable) !== 'string'
            || ! Object.hasOwn(variables, data.variable)
        ) {
          throw new Error(`Unrecognized variable [${data.variable}]`);
        }
    
        return variables[data.variable];
      }
    
      // Note: you may also want a mechanism for including objects that *aren't*
      // React elements but that may *contain* elements and/or variables. For
      // example, something like
      // { "object": { "foo": { "variable": "lhospital" } } }
      // could be the equivalent of { {foo: lhospital} } in JSX. This
      // implementation, however, doesn't support that; the only way to include a
      // generic object is with 'raw' (see above), which also prevents any further
      // substitutions.
    
      throw new Error(
        'Unrecognized kind of object: ' + JSON.stringify(data)
      );
    }
    

    and could be used like this:

    import MathJax from "react-mathjax";
    import { lhospital } from "./equations/equations";
    
    function App() {
      const dataStr =
        '['
        + '{"element":{"type":"strong","children":["L'hospital"]}},'
        + '": ",'
        + '{"element":{"type":"MathJax.Node","props":{"formula":{"variable": "lhospital"}}}}'
        + ']';
    
      const children = useMemo(
        () =>
          performSubstitutions(
            JSON.parse(dataStr),
            { 'lhospital': lhospital },
            { 'MathJax.Node': MathJax.Node }
          ),
        [dataStr, lhospital]
      );
    
      return (
        <MathJax.Provider>
          {children}
        </MathJax.Provider>
      );
    }
    
    export default App
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search