Search code examples
javascriptreactjsmathjax

Is there a way to access the properties of a an imported component upon its drawing is complete in React?


I am developing a UI where one can compare an image of a mathematical formula with a rendered version of the LaTeX code that was generated from that image via a DL model. For this, I want to place the two elements (sourceImage and rendered code) one below the other. Now, to simplify the comparison, it would be beneficial to have the two elements in the same size. For rendering the latex code I use the package better-react-mathjax. With this package, I unfortunately only have the option to set the font size, not the width of the resulting rendered component. Hence, I am wondering whether I can set the width of the image element based on the width of the rendered code somehow. I tried to accomplish that with refs, these cannot be used on the MathJax component however.

       <Grid container>
          <Grid item>
            <fieldset>
              <legend>Original formula image</legend>
              <Box>
                <img
                  width={imageWidth}
                  src={sourceImage}
                  alt="Input image"
                />
              </Box>
            </fieldset>
          </Grid>
          <Grid item>
            <fieldset>
              <legend>Dynamically rendered (corrected) formula</legend>
              <Box>
                <MathJax className={classes.mathjaxElement} dynamic={true}>{'$$' + texSource + '$$'}</MathJax>
              </Box>
            </fieldset>
          </Grid>
        </Grid>

EDIT:

To be more precise on what I want to achive, the following is an image of what the above code renders to currently. What I'm trying to achive is that the image in the upper box has the same width as the formula in the bottom one (the width of which is defined by the formula in it).

Image with the current rendering output


Solution

  • Ok, so this involves a lot of React and web "stuff" for it to work.

    First of all, better-react-mathjax is my library and it is true that we cannot pass a ref to its MathJax component. Thanks to your post however, I will make sure to enable callback refs in a future release (e.g. a function which takes a ref and does something with it). The ownership of the ref must stay in the MathJax class however since it is crucial for the component to work.

    Nonetheless, with your use-case in mind, it is not really necessary to attach the ref to the MathJax component itself; all you want to know is the width of the Latex content and this can be accomplished simply by putting the MathJax component in another element which grows with its content. Any problems arising with this method would likely arise with the method of attaching the ref directly on the MathJax component (if it would be possible) so here, the lack of this functionality in the MathJax component is not the problem.

    So the plan is for the image to have the same width as the resulting Latex. Not well that there are some inherent problems with this that we will likely have to accept:

    • We don't know the padding of the math in the image. I assume the end goal is for the formulas to have similar size but if the image itself has the formula centered inside a lot of whitespace, then adjusting the image size to the output Latex size will not necessarily make the math the same size.
    • You WILL have to pick some font size for the output Latex. Maybe you could determine this dynamically from the size of the input image but that's a different chapter. As a result, the resulting size might always differ from the input size since we don't know anything about its size (you COULD use transform to scale the element to some size and thereby circumvent the problem of picking a suitable font size, but I'm not sure if this would be optimal either).

    Given that we accept the above, the plan is as follow:

    1. Render the Latex inside an element which grows with its content.
    2. Measure the resulting width of the element containing the Latex.
    3. Set the width of the image to be equal to the width fetched in the last step.

    This can be done in many ways. As you have already seen, the resulting element width might not be the final width when a useEffect fires so to get around that, we could set a timeout to trigger some short time after the element has rendered at which point the final width should be readily available:

    setTimeout(
        () => setWidth(ref.getBoundingClientRect().width),
        100
    );
    

    Other important details are that the element containing the Latex should be an element that expands with its content, e.g. not a block element for example.

    I have prepared a sandbox for you as a proof of concept: https://codesandbox.io/s/user-example-16-so73825659-wkgd96

    Try to click the button and verify that when the math changes, the image also changes its width.

    Hope you can generalize from it and solve your use-case!

    UPDATE

    A different technique you can use is:

    • Render the SAME Latex math in both the upper and lower container. Set the upper container to position: relative.
    • Add an element with position: absolute to the upper container. Make this element stretch all the way to the edges of its container (using top: 0, left: 0, right: 0 and bottom: 0). Add a z-index to make sure that it appears in front of the Latex rendered in the background.
    • Make the background of this element some other color so that it covers the Latex generated behind.
    • Add the image, either as a background or as a regular image, in this absolutely positioned upper element.

    This works by the upper element taking its size from the generated Latex (thus the upper and lower container will have the same size). The content can then be adapted to this size either by means of a background image or as a regular image. This works without any timeout magic and might be seen as a more "natural" solution :)

    Here is a sandbox that demonstrates both techniques: https://codesandbox.io/s/user-example-16b-so73825659-4u9ug1