MermaidJS is one of the most useful tools for visualizing your ideas, charts, and data. In my current generative AI application, it combines MermaidJS with ReactJS and React Query. There wasn't any guidance available online when I implemented it, so today's post details how I integrated MermaidJS + React + React Query.
First we need to install these package by :
npm install @tanstack/react-query react react-dom mermaid @types/mermaid typescript
For different app's purposes, I will create a <MermaidRenderer />
for rendering MermaidJS library. it includes these steps:
import mermaid, { MermaidConfig } from 'mermaid';
export interface MermaidRendererProps {
graphText: string;
}
export const MermaidRenderer: React.FC<MermaidRendererProps> = ({ graphText }) => {
return (
// the implementation goes here
);
}
While Mermaid is a powerful library that supports multiple chart types, integrating it with React presents some challenges:
These requirements somewhat headache to me. But we will resolve it together. before any requirement, how to render mermaid in ReactJS's world. I've researched for days and found out how to do it.
Here's how MermaidJS rendering works in React:
<svg class="mermaid">
elements.<svg class="mermaid">
elements with the startOnLoad
attribute set.you can read more about the configuration here https://mermaid.js.org/config/setup/interfaces/mermaid.MermaidConfig.html
import mermaid, { MermaidConfig } from 'mermaid'; const mermaidConfig: MermaidConfig = { startOnLoad: false, // this is important to stop rendering chart on page load (reactjs re-renders multiple times). theme: 'default', flowchat: {...}, sequence: {...}, pie: {...} } mermaid.initialize(mermaidConfig);
Because the mermaid renders based on RealDOM so it must be happened after the page rendered, so we will use useLayoutEffect
hook for this render.
import mermaid, { MermaidConfig } from 'mermaid';
export interface MermaidRendererProps {
graphText: string;
}
export const MermaidRenderer: React.FC<MermaidRendererProps> = ({ graphText }) => {
const [graph, setGraph] = React.useState<string>('');
const renderGraph = useCallback(async () => {
try {
const isChartValid = await mermaid.parse(graphText);
if (!isChartValid) throw new Error("Invalid chart");
const { svg: svgCode }: RenderResult = await mermaid.render("id", graphText);
setChart(svgCode);
} catch (error) {
// TODO: Handle your custom error
console.error(error);
} finally {
setLoading(false);
}
}, [graphText]);
useLayoutEffect(() => {
renderGraph();
}, [renderGraph]);
return (
<div className={styles.svgContainer} dangerouslySetInnerHTML={{__html: chart ?? ""}} />
);
}
Okay then we are able to render the chart. Homework for you :D
If you don't have time or just simply want to quickly read the solution then let continue.
For this requirement, I use svg-pan-zoom
package (https://www.npmjs.com/package/svg-pan-zoom). Then we can simply make a configuration and use svgPanZoom
method from the package to apply this feature.
const svgPanZoomConfig: SvgPanZoom.Options = { panEnabled: true, zoomEnabled: true, fit: true, center: true, minZoom: 0.5, maxZoom: 5, zoomScaleSensitivity: 0.1, dblClickZoomEnabled: true, preventMouseEventsDefault: true, }; export const MermaidRenderer: React.FC<MermaidRendererProps> = ({ graphText }) => { const containerRef = useRef<HTMLDivElement | null>(null); const [isLoading, setLoading] = useState<boolean>(true); /* ... */ const applyPanAndZoom = useCallback(() => { if (containerRef.current && chart) { const svgElement = containerRef.current.querySelector("svg"); if (svgElement) { const panZoomInstance = svgPanZoom(svgElement, svgPanZoomConfig); panAndZoomInstanceRef.current = panZoomInstance; } } }, [chart]); useLayoutEffect(() => { applyPanAndZoom(); }, [applyPanAndZoom]); return ( <div className={styles.container} ref={containerRef}> { isLoading ? <div>Loading...</div> : <div ref={svgContainerRef} dangerouslySetInnerHTML={{__html: chart ?? ""}} />} </div> ) };
Because of behaviors like panning and zooming, combination This feature needs some adjustments
html2canvas
(https://www.npmjs.com/package/html2canvas/v/1.4.1) to create a canvas element from the SVG. const resetPanAndZoom = useCallback(() => {
const panZoomInstance = panAndZoomInstanceRef.current;
if (chart && panZoomInstance) {
panZoomInstance.reset();
}
}, [chart]);
const downloadByDataUrl = useCallback((dataUrl: string, name: string = "graph") => {
const link = document.createElement("a");
link.href = dataUrl;
link.download = `${name}.png`;
link.click();
}, []);
const downloadGraph = useCallback(async () => {
resetPanAndZoom();
if (svgContainerRef.current && chart) {
const canvas = await html2canvas((svgContainerRef.current as HTMLElement), { backgroundColor: 'transparent' });
if (canvas) {
const dataUrl = canvas.toDataURL("image/png");
downloadByDataUrl(dataUrl);
}
}
}, [chart, downloadByDataUrl, resetPanAndZoom]);
Well, If you go to this point, I hope that the solution make your life easier or helpful at least. The complete, working solution is available on my GitHub for your reference https://github.com/tuanhuydev/mermaid-react-integration. As always, I'm tuanhuydev, a professional software engineer from Vietnam. I'm passionate about contributing to projects and businesses, so if you have any needs, feel free to reach out. Thank you for your read and I will see you in the next post.#tuanhuydev