README
A simple React component for MathJax
Up-to-date component for using MathJax in latest React (using functional components and hooks API). Focuses on being versatile and making the use of MathJax in React a pleasant experience without flashes of non-typeset content, both with respect to initial rendering as well as dynamic updates. Simple to use but with many configuration options.
Basic workflow
better-react-mathjax introduces two React components - MathJaxContext and MathJax. For MathJax to work with React,
wrap the outermost component containing math (or the entire app) in a MathJaxContext component. Then simply use MathJax components at
different levels for the actual math. In the typical case, the content of a MathJax component can be everything from a
subtree of the DOM to a portion of text in a long paragraph. The MathJaxContext is responsible for downloading MathJax
and providing it to all wrapped MathJax components that typeset math.
Features
- Supports both MathJax version 2 and 3.
- Supports local copy of MathJax or copy supplied via CDN.
- Small imprint on production bundle with dependencies only for types (image shows a size of 7.32 KB and 2.37 KB gzipped in a NextJS project analyzed with their bundle analyzer).
- Built in a modular fashion on top of MathJax with direct access to MathJax via the MathJax configuration.
- Use MathJax functionality either through the
MathJaxcomponent or by yourself through theMathJaxBaseContext. - Either put your math into the DOM with React first and let MathJax typeset afterwards (v. 2 and 3), or typeset with MathJax first and add it to the DOM afterwards (v. 3 only).
- Hide your components before they are typeset to avoid flashes of non-typeset content and make the use of MathJax a pleasant experience.
Installation
Add this library manually as a dependency to package.json...
dependencies: {
"better-react-mathjax": "^1.0.3"
}
... and then run npm install or let npm or yarn do it for you, depending on which package manager you have
chosen to use:
# npm
npm install better-react-mathjax
# yarn
yarn add better-react-mathjax
Examples
The first 3 are basic examples with zero configuration standard setup using MathJax version 3 with default MathJax config and no extra options. Note that sandboxes tend to be slower than use in a real environment.
Example 1: Basic example with Latex
Standard setup using MathJax version 3 with default MathJax config and no extra options.
export default function App() {
return (
<MathJaxContext>
<h2>Basic MathJax example with Latex</h2>
<MathJax>{"\\(\\frac{10}{4x} \\approx 2^{12}\\)"}</MathJax>
</MathJaxContext>
);
Sandbox: https://codesandbox.io/s/better-react-mathjax-basic-example-latex-bj8gd
Example 2: Basic example with AsciiMath
Using AsciiMath requires importing a specific loader (see the MathJax documentation for further information).
AsciiMath uses the same display mode on the entire page, which is display math by default.
It can be changed to inline math by adding asciimath: { displaystyle: false } to the input config.
export default function App() {
const config = {
loader: { load: ["input/asciimath"] }
};
return (
<MathJaxContext config={config}>
<h2>Basic MathJax example with AsciiMath</h2>
<MathJax>{"`frac(10)(4x) approx 2^(12)`"}</MathJax>
</MathJaxContext>
);
}
Sandbox: https://codesandbox.io/s/better-react-mathjax-basic-example-asciimath-ddy4r
Example 3: Basic example with MathML
MathML is supported natively by a few but far from all browsers. It might be problematic to use with Typescript (no types for MathML included in this package).
export default function App() {
return (
<MathJaxContext>
<h2>Basic MathJax example with MathML</h2>
<MathJax>
<math>
<mrow>
<mrow>
<mfrac>
<mn>10</mn>
<mi>4x</mi>
</mfrac>
</mrow>
<mo>≈</mo>
<mrow>
<msup>
<mn>2</mn>
<mn>12</mn>
</msup>
</mrow>
</mrow>
</math>
</MathJax>
</MathJaxContext>
);
}
Sandbox: https://codesandbox.io/s/better-react-mathjax-basic-example-mathml-20vv6
Example 4: Elaborate example with Latex
Sandbox: https://codesandbox.io/s/better-react-mathjax-example-latex-3vsr5
Example 5: Elaborate example with AsciiMath
Sandbox: https://codesandbox.io/s/better-react-mathjax-example-asciimath-p0uf1
Example 6: Elaborate example with MathML
Sandbox link: https://codesandbox.io/s/better-react-mathjax-example-mathml-nprxz
Make sure to study the comments in this file as MathML processing is a little bit different from Latex and AsciiMath.
Example 7: Elaborate example with optimal settings for dynamic updates with Latex
Sandbox link: https://codesandbox.io/s/better-react-mathjax-example-latex-optimal-8nn9n
Under the hood
The MathJaxContext component downloads MathJax and provides it to all users of the MathJaxBaseContext, which includes
MathJax components. A MathJax component typesets its content only once initially, if the dynamic flag
is not set, in which case the content is typeset every time a change might have occurred. To avoid showing the
user flashes of non-typeset content, MathJax does its work in a layout effect,
which runs "before the browser has a chance to paint". Nevertheless, since typesetting operations are asynchronous, both
because the MathJax library needs to be downloaded but also because MathJax should typeset asynchronously to not block
the UI if it has a lot to typeset, the typesetting taking place before the browser paints the updates cannot be guaranteed.
In most situations however, it should.
TypeScript types
This project has both its own types and MathJax types included in the package. For MathJax version 2, a refactored and updated
version of @types/mathjax is used whereas for MathJax version 3, this package
depends on the types from mathjax-full. Nonetheless, none of the logic from
these are used in this project so after building production code and tree-shaking, these dependencies will not affect the
size of the final bundle. If you would prefer a separate @types package for this project, please make a suggestion about this in an issue on the
project Github page. Note also that issues with the MathJax 2 types can be addressed and updated within this project whereas
the types from mathjax-full are used unaltered.
The MathJax types are not always helpful and the user should pay attention even if the compiler does not
complain. First of all, several of the types from mathjax-full contain catch-all properties of the form
[s: string]: any which effectively allows any props to be passed in. Hence, adding a MathJax 2 configuration to a MathJaxContext
using MathJax version 3 will not result in a compile error but instead be accepted even though most of the props won't
have the desired effect in MathJax 3.
Also, due to how TypeScript handles excess properties,
if a configuration is given in a variable (as opposed to in a literal) where any property matches a property of the required type,
the remaining props will be silently ignored. Since MathJax versions share a few configuration properties, it is therefore
also possible that a MathJax 3 configuration may be given to a MathJaxContext using MathJax 2 without compiler errors. This
can however be avoided by always using literals in which case excess properties are handled differently.
API
The following three properties can be set on both the MathJaxContext and MathJax components. When set on a
MathJaxContext component, they apply to all wrapped MathJax components except those on which the property in
question is set on the individual MathJax component, which then takes precedence.
Note: MathJax3Object and MathJax3Config are aliases for MathJaxObject and MathJaxConfig
as exported by mathjax-full.
hideUntilTypeset: "first" | "every" | undefined
Controls whether the content of the MathJax component should be hidden until after typesetting is finished. The most useful
setting here is first since the longest delay in typesetting is likely to occur on page load when MathJax hasn't loaded
yet. Nonetheless, with a large amount of math on a page, MathJax might not be able to typeset fast enough in which case
non-typeset content might be shown to the user; in this case the setting of every might be handy.
Default: undefined (no content is hidden at any time)
first: TheMathJaxcomponent is hidden until its content has been typeset the first time after which the component remains visible throughout its lifetime.every: The same behaviour as when this property is set tofirst, but in addition, theMathJaxcomponent is now hidden and made visible every time it is typeset. WithrenderModeset toprethis has no effect and is treated asfirst. WhenrenderModeis set topost, the component is typeset anew on every render. When MathJax is able to typeset fast enough (which is most often the case), the updates will be seamless and the hiding will be invisible to the human eye. When this is not the case this setting might result in "blinking" content as an alternative to flashes of non-typeset content.
renderMode: "pre" | "post" | undefined
Controls how typesetting by MathJax is done in the DOM. Typically, using the setting of post works well but in rare cases
it might be desirable to use pre for performance reasons or to handle very special cases of flashes of non-typeset content.
Default: post
post: Allchildrenof theMathJaxcomponent are added to the DOM by React first and then MathJax processes the wrapped content (in the DOM). This implies that MathJax cannot know if the content has changed or not between renderings and so typesetting takes place on every render. This mode might give rise to flashes of non-typeset content since the content could enter the DOM before MathJax has typeset it (if MathJax doesn't have time to typeset fast enough). In thisrenderModeMathJax can inspect the context in the DOM and adapt its output to it in different ways (for example in terms of font size).pre: Math is passed via thetextproperty (only strings), which must be set with math without delimiters, and is processed by MathJax before it is inserted into the DOM. This mode also requirestypesettingOptionsto be set with the name of the function to use for the typesetting as well as an optional configuration object with typesetting details. In thisrenderMode, MathJax only typesets when thetextproperty changes. Since MathJax cannot look at the context (in the DOM) of the math, limited automatic adaptation to surrounding content can be accomplished and fine-tuning might have to be done via the optionaloptionsobject of thetypesettingOptionsproperty. Note: Theprevalue can only be used with MathJax version 3.
typesettingOptions: { fn: TypesettingFunction, options: OptionList | undefined } | undefined
Used to control typesetting when renderMode is set to pre. Controls which typesetting function to use and an optional
object with typesetting details.
Default: undefined (no conversion function is supplied which throws an error when renderMode is pre)
fn: The name of the MathJax function to use for typesetting. This is only used, and must be specified, whenrenderModeis set topreand should be one of the following strings:tex2chtml,tex2chtmlPromise,tex2svg,tex2svgPromise,tex2mml,tex2mmlPromise,mathml2chtml,mathml2chtmlPromise,mathml2svg,mathml2svgPromise,mathml2mml,mathml2mmlPromise,asciimath2chtml,asciimath2chtmlPromise,asciimath2svg,asciimath2svgPromise,asciimath2mmlorasciimath2mmlPromise. The value is the name of a function that MathJax generates based on the input configuration, as given to the wrappingMathJaxContext, as per the docs. For example, for thetex2chtmlfunction to be available, the configuration given to theMathJaxContextcomponent must (explicitly or by use of default) contain a Latex input processor and an HTML output processor.options: An object with additional parameters to control the typesetting whenrenderModeis set topre. Since this typesetting is done outside of the DOM context in which the resulting math will be inserted, MathJax cannot adapt the output to the surrounding content, which is why this can be done manually by the typesetting function. More information about this object can be found in the the docs.
MathJaxContext component
config: MathJax2Config | MathJax3Config | undefined
Controls MathJax and is passed to MathJax as its config.
Default: undefined (default MathJax configuration is used)
MathJax configuration object. Make sure it corresponds to the version used. More information can be found in the docs.
src: string | undefined
The location of MathJax.
Default: undefined (default CDN https://cdnjs.cloudflare.com is used)
Local or remote url to fetch MathJax from. More information about hosting your own copy of MathJax can be found
in the MathJax documentation and more in particular on
the better-react-mathjax Github page.
A source url may contain both some specific file and some query parameters corresponding to a configuration which, in turn, governs which additional assets MathJax fetches. The default sources used when this property is omitted are the same as those listed in the MathJax instruction (however from a different CDN). These correspond to some typical and broad use of MathJax. If you have a use case where you, using standalone MathJax, would have to use a different source url, then you have to manually supply such a url (local or remote) here. This, in analogy to how you would modify the script import to adjust to your needs in a plain HTML environment with direct use of MathJax. Read more about different configurations here (for MathJax 3) and here (for MathJax 2).
version: 2 | 3 | undefined
MathJax version to use. Must be synced with any config passed.
Default: 3
Version of MathJax to use. If set, make sure that any configuration and url to MathJax uses the same version. If src
is not specified, setting versionto 2 currently makes use of version 2.7.9 and setting it to 3 uses 3.2.0.
onStartUp((mathJax: MathJax2Object | MathJax3Object) => void) | undefined
Callback to be called when MathJax has loaded successfully but before the MathJax object has been made available
to wrapped MathJax components. The MathJax object is handed as an argument to this callback which is a good place
to do any further configuration which cannot be done through the config object.
Default: undefined
onLoad(() => void) | undefined
Callback to be called when MathJax has loaded successfully and after the MathJax object has been made available to the
wrapped MathJax components. This marks the last step of the startup phase in the MathJaxContext component when
MathJax is loaded. Can be used to sync page loading state along with onInitTypeset callbacks on MathJax components.
Default: undefined
onError((error: any) => void) | undefined
Callback to handle errors in the startup phase when MathJax is loaded.
Default: undefined
MathJax component
inline: boolean | undefined
Whether the wrapped content should be in an inline or block element. When renderMode is post, this refers to the
wrapper component that this MathJax component uses (the user might still have both display and inline math inside).
If renderMode is set to pre this property applies to both the wrapper component and the content which will be typeset
as inline math if this property is set to true and as display math otherwise.
Note: Currently only MathML and Latex can switch between inline mode and math mode in the same document. This means that AsciiMath will use the document default for content, no matter the setting of this property. The property will still affect the wrapper nonetheless.
Default: false
onInitTypeset(() => void) | undefined
Callback for when the content has been typeset for the first time. Can typically be used for hiding content or showing a loading spinner in a coordinated way across different elements until all are in a representative state.
Default: undefined
onTypeset(() => void) | undefined
Callback for when the content has been typeset (not only initially). Can typically be used for hiding content or showing
a loading spinner in a coordinated way across different elements until all are in a representative state. Only used when
the dynamic flag is set. Similarly to onInitTypeset, this callback also fires on initial typesetting. If the dynamic
is not set, this callback is effectively reduced to having the same effect as onInitTypeset. When the dynamic flag is
set, this callback runs after every typesetting, which takes place on every render if renderMode is set to post, and
when the text prop changes when renderMode is set to pre.
Default: undefined
text: string | undefined
Required and only used when renderMode is set to pre. Should be the math string to convert without any delimiters.
Requires typesettingOptions to be set and version to be 3. If renderMode is post, this property is ignored.
Default: undefined
dynamic: boolean | undefined
Indicates whether the content of the MathJax component may change after initial rendering. When set to true,
typesetting should be done repeatedly (every render with renderMode set to post and whenever the
text property changes with renderMode set to pre). With this property set to false, only initial typesetting will
take place and any changes of the content will not get typeset.
Default: false
Any additional props will be spread to the root element of the MathJax component which is a span with display
set to inline when the inline property is set to true, otherwise block. The display can be overriden via
style or className props if needed (then the inline property does not affect the wrapper). A ref is not possible to set
as this functionality is used by the MathJax component itself.
Custom use of MathJax directly
You can use the underlying MathJax object directly (not through the MathJax component) if you want as well. The
following snippet illustrates how to use MathJaxBaseContext to accomplish this.
// undefined or MathJaxSubscriberProps with properties version, hideUntilTypeset, renderMode, typesettingOptions and promise
const mjContext = useContext(MathJaxBaseContext)
if(mjContext)
mjContext.promise.then(mathJaxObject => { // do work with the MathJax object here })
This requires only a MathJaxContext, supplying the MathJaxBaseContext, to be in the hierarchy. The object passed from the promise property is the MathJax
object for the version in use.
Sandbox example: https://codesandbox.io/s/better-react-mathjax-custom-example-latex-e5kym
Fighting flashes of non-typeset content
Using MathJax, as is, is as seen from the basic examples above fairly simple, but the real challenge is to use it in a way so that the user doesn't see flashes of non-typeset content. Apart from making MathJax available to React in a simple and straightforward way, this is what this library focuses on.
Static content
Static content does not have the dynamic property set to true and is typeset once only when the component mounts. If the
component remounts, the procedure repeats. Before the content is typeset, the user may see the raw content which might be
a negative experience. There are several ways to solve this:
- Set
hideUntilTypeSettofirston individualMathJaxcomponents or on theMathJaxContext. - Coordinate hiding with
onInitTypesetto show bigger blocks or the entire page once allMathJaxcomponents have finished the initial typesetting. Coordinate withMathJaxContextvia theonStartUporonLoadcallback.
Dynamic content
Dynamic content might be harder to work with since it, per definition, updates several times during the time a MathJax
component is mounted. With this goal, the dynamic property should be set to true which implies that typesetting will
be attempted repeatedly (after every render if renderMode is set to post and when the text property changes
if renderMode is set to pre). If not handled correctly, updates might look bad to the user if the content is
visible before typesetting. As indicated above in the "Under the hood" section, this should usually not happen since MathJax
typesets the content in a layout effect. However, MathJax typesets content asynchronously and there might be occasions
where the typesetting takes place after the browsers has already updated. This might happen if you have a lot of math on
a page for example. Apart from the general considerations below, there are a few strategies to try in order to solve
this problem.
Note: these measures should only be taken to battle flashes of non-typeset content where proven necessary.
- Set
hideUntilTypesettoevery. This might result in a short blink instead but may be a preferable option in some cases than to show content that is not typeset. Try to put yourMathJaxcomponents outside of parents that often rerender to avoid unnecessary rerenderings (and accompanying blinking). - Set
renderModetopre. With this mode, theMathJaxcomponent typesets the math content before inserting it into the DOM which should remove any flashes of non-typeset content in some scenarios, however not in all as indicated below. One downside with this setting is that MathJax cannot access the context of the math and so it cannot adapt generated content to it; manual fine-tuning might be necessary even though this is not always the case.
General considerations regarding flashes of non-typeset content
- Currently, MathJax version 3 might give rise to subtle flickering in both Chrome and Safari on dynamic updates. This is not connected to this package and does not seem to be related to the actual typesetting but how the CSS is injected into and applied by the browser. Alas, flickering may in some cases be visible despite described methods. In this case, the remedy is to use version 2, where seamless typesetting is still possible in all attempted browsers. Note that this kind of flickering is not a flash of non-typeset content but merely some styling adjustment that is done after typesetting.
- The best cross-browser experience for normal use cases at this time is achieved with version 2, with disabled
fast-previewandprocessSectionDelayset to0for a smooth experience. This is done by including"fast-preview": { disabled: true }in the MathJax config object given to theMathJaxContextand addingmathJax => mathJax.Hub.processSectionDelay = 0as theonStartupcallback to the same. Coordinate initial typesetting withhideUntilTypesetset tofirstand / oronInitTypesetcallbacks. Feel free to check out Example 7 above where this is shown, but remember, don't use it if you dont need to.
General Considerations (don't skip)
Don't nest
MathJaxcomponents since this will result in the same math content being typeset multiple times by differentMathJaxcomponents. This is unnecessary work.React has an unresolved issue with DOM nodes with mixed literal and expression content, such as
<div> This is literal and { "this is in an expression" }</div>, when used together with DOM manipulation via refs. For this reason, when thedynamicproperty is set totrue, always make sure that the expression containing math is not mixed with literal content. The following list summarizes this:- Don't:
<p>An example is the equation ${num}x^4 = 100
- Don't: