How to Display Math Equations in React Native

How to Display Math Equations in React Native

Β·

5 min read

One of the most fantastic features of Hashnode is that it allows you to display and highlight your content in various ways.

One of my personal favorites is the support for Latex expressions.

Why is that?

Math expressions are an essential part of information processing. Every computer runs on calculations of different things. To highlight some circumstances, there is almost no better way than to show a graph or use mathematical expressions to underline your statement.

This is even more true when viewing one of the hottest topics within computer science: machine learning. Here, many algorithms used can be displayed as mathematical expressions for better understanding and compromising of information.

Math

In the latest release of the Hashnode mobile app, we added support for the native rendering of blog content. This happens by translating the HTML from the blog post to native views in React Native.

To give you folks the same experience in the app as on our website, we want to display everything comprehensively and beautifully. One of the critical things here are these mathematical expressions.

What's the premise?

Consider the following three items to display an equation:

  1. Sum the power of i on each iteration until reaching n
  2. \( \sum_{i=0}^n i^2 \)
  3. sum_{i=0}^n i^2

Which of these representations of a mathematical phenomenon is more easily readable? It's the second one. Mathematical symbols are common, and it is quickly understood what the former does, as all used symbols are standardized in their meaning.

The Hashnode editor allows you to display a mathematical equation differently by using Latex. You can add an expression inline with \\( YOUR_EXPRESSION_HERE \\) or as a separate block with:

$$  
YOUR_EXPRESSION_HERE  
$$
--OR--
\\[
YOUR_EXPRESSION_HERE  
\\]

In the background, this will get wrapped in either a <p> or <li> tag, depending on where your place your equation.

On the mobile site, we are using react-native-render-html to render the content of a blog in native views.

We are using MathJax to display equations on the Web. The library for rendering HTML on React Native does not support mathematical expressions out of the box.

To get this to work on the mobile side, we need another library that can handle these expressions and display them customizable to our needs: a component that lets us display mathematics in HTML using MathJax without WebView.

Rendering of expressions

How does the rendering work now? For Text in articles, we have multiple renderers depending on various things to display information. So, a simple solution could be not to use a <Text> component to render all text within the blog post but use the <MathJaxSvg> component.

This comes with some downsides, even though only math is rendered as an SVG:

  1. No custom styling applicable to the underlying Text component
  2. No custom font handling
  3. No handling of li elements.

A remarkable feature of react-native-render-html is that you can add custom renderers for each HTML tag, even entirely new tags that are unique to your application.

We used this feature and devised a neat trick to render math on our app.

The core idea is to find all occurrences of mathematical expressions and wrap them in our newly introduced <math> HTML tag to register a renderer for the tag that will use MathJaxSvg to display math equations.

Getting the information

We are using regular expressions to find occurrences of Latex math expressions in the content of an article. We have two distinct expressions that can find a block or inline expressions within the content:

export const MATH_JAX_PATTERN_INLINE_BLOCK =
  /(?:\${2}|\\\[|\\(begin|end)\{.*?})[^<]*(?:\${2}|\\\]|\\(begin|end)\{.*?})/g;

export const MATH_JAX_PATTERN_INLINE =
  /<\s*\s*.*\s*>*(?:\\\(|\\(begin|end)\{.*?})[^<]*(?:\\\)|\\(begin|end)\{.*?})*<\s*\/\s*.*\s*>/g;

We can now match each pattern against the content and wrap each match within a <math> EXPRESSION </math> tag.

With the updated content that is now passed to our rendering engine, we can proceed with adding the actual renderers for the newly created tag.

Display the results

First, we need to add our new tag so that the rendering library recognizes it:

 const customHTMLElementModels: HTMLElementModelRecord = {
    math: HTMLElementModel.fromCustomModel({
      tagName: 'math',
      contentModel: HTMLContentModel.block,
    }),
    ... other tags
 };

Then, we can continue with creating the actual renderer for math.

const BLOCK_PATTERN = /(?:\${1}|\\\[|\\(begin|end)\{.*?})/g;

const MathJaxRenderer: CustomBlockRenderer = props => {
  const theme = useThemeContext();
  const { TDefaultRenderer, ...restOfTheProps } = props;
  const {
    tnode: { domNode },
  } = props;
  const html = React.useMemo(() => domNodeToHTMLString(domNode), [domNode]);

  const isBlock = !!html.match(BLOCK_PATTERN);
  return (
    <MathJaxSvg
      fontSize={DEFAULT_FONT_SIZE}
      color={theme.colors.para}
      fontCache
      style={StyleSheet.flatten([
        {
          backgroundColor: 'transparent',
          alignItems: 'center',
        },
        isBlock
          ? {
              justifyContent: 'center',
              marginVertical: calculateFontSize(10),
            }
          : {
              justifyContent: 'flex-start',
              marginVertical: calculateFontSize(7),
            },
      ])}>
      {html}
    </MathJaxSvg>
  );
};

As you can see here, we are additionally checking if the currently rendered expressions use a block or inline syntax to add the proper margin to the container.

To get the actual expressions, we are converting the node generated by the renderer back to HTML text using a helper function.

Finally, we can register this new renderer within our custom renderers configuration:

const renderers: CustomTagRendererRecord = {
  math: MathJaxRenderer,
  ... other renderers 
};

Bringing it all together

We can now create the rendering engine that will use MathJaxSvg to render everything wrapped inside a math tag.

const Engine = () => {
  return (
     <TRenderEngineProvider
        systemFonts={systemFonts}
        customHTMLElementModels={customHTMLElementModels}>
        <RenderHTMLConfigProvider
          renderers={renderers}>
          {children}
        </RenderHTMLConfigProvider>
      </TRenderEngineProvider>
  )
}

Our final result of how beautiful math can be rendered in an application looks like this:

MathJax rendering in App

With the same technique, you can render other custom elements in your own HTML-powered React Native application.

I hope you enjoyed this article. If you are interested in more content about HTML rendering and similar stuff, leave a comment below! 😊

Download the Hashnode app now if you haven't already. We have an exciting roadmap ahead, and our app will receive some cool updates soon.

Until next time, see ya!