Recently, while developing LogoDash—a logo generator designed to help creators and indie developers quickly generate minimalist logos—I ran into an interesting issue. After updating the code to use React 18’s API, the PNG export feature suddenly stopped working. After some debugging, I discovered that this was due to React 18’s asynchronous rendering behavior. Let me share the story and the solution.
LogoDash is a tool I built to help users generate simple, beautiful logos with just one click. Users can customize icons from libraries like Font Awesome and Lucide, adjust colors, text, gradients, and more. Alternatively, they can use the "Random Generate" feature to quickly create minimalist gradient-style logos. The goal is to help entrepreneurs and indie developers launch their products faster by providing a quick, hassle-free logo solution.
One of LogoDash’s key features is the ability to export designs as PNG or SVG files. However, after a recent code update, the PNG export feature broke. Here’s how I fixed it.
In LogoDash, the PNG export feature relies on rendering an SVG element to a temporary container and then drawing it onto a canvas. The original code used ReactDOM.render
, which is now deprecated in React 18:
typescript// Old code (deprecated in React 18) ; const tempContainer = document.createElement('div'); ReactDOM.render(iconElement, tempContainer); // Warning: ReactDOM.render is no longer supported const const svgElement = tempContainer.querySelector("svg");
Following React 18’s recommendations, I updated the code to use createRoot
:
typescript// Updated code (following React 18 guidelines) import ReactDOM from "react-dom/client"; const tempContainer = document.createElement('div'); const root = ReactDOM.createRoot(tempContainer); root.render(iconElement); // No warning, but the SVG element was missing! const svgElement = tempContainer.querySelector("svg"); // svgElement was empty
After this change, the exported PNG files only showed the background—the icon itself was missing.
When I logged svgElement
, its innerHTML
was empty. This meant that the icon element wasn’t rendered into the temporary container by the time I tried to access it.
In other words, the issue was a race condition: I was trying to read the DOM before React had finished writing to it.
React 18’s rendering is asynchronous by default. According to the official React documentation:
"Although rendering is synchronous once it starts,
root.render(...)
is not. This means code afterroot.render()
may run before any effects of that specific render are fired."
To ensure the DOM is updated before accessing it, React provides the flushSync
API, which forces synchronous updates. Here’s how I fixed the code:
typescriptimport { flushSync } from "react-dom"; // Fixed code const root = ReactDOM.createRoot(tempContainer); flushSync(() => { root.render(iconElement); }); // Now the SVG element is safely available const svgElement = tempContainer.querySelector("svg");
By wrapping the root.render()
call in flushSync
, I forced React to complete the rendering process immediately, ensuring the SVG element was available when I needed it. This restored the PNG export functionality.
This issue highlights the importance of understanding React 18’s asynchronous rendering behavior. Here are some key lessons:
root.render()
is asynchronous by default. Don’t assume the DOM updates immediately after calling it.flushSync
when you need to access the DOM right after rendering. This ensures the render is completed synchronously.I hope this helps other developers who encounter similar issues with React 18!