I am using React to render some SVG elements that include filters. Here is a simplified example:
function ShadowCircle() {
return (
<g>
<defs>
<filter id="dropShadow" x="-20%" y="-20%" width="200%" height="200%">
<feOffset result="offOut" in="SourceAlpha" dx="1" dy="1" />
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="1" />
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
</filter>
</defs>
<circle r="4" cx="50" cy="50" fill="blue" filter="url(#dropShadow)" />
</g>
);
}
function App() {
return <svg width="100" height="100">
<ShadowCircle />
</svg>
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This works fine as long as I only render a single instance of my <ShadowCircle />, otherwise I end up with duplicate filters (and duplicate IDs) in the DOM.
Ideally I would like to be able to:
- Ensure that only one filter is rendered whenever one or more shadow circles are rendered
- Remove the filter when it is not in use
I have tried a custom approach using createPortal and the useEffect hook, but I faced a couple of problems:
- It is not possible to render SVG Elements without wrapping them in
<svg>tags, so the portal has to be a<div>and all my filters end up in separate<svg>documents. - Calling
getElementByIdinsideuseEffectto check whether the filter was already on the page turned out to be unreliable, especially when multiple elements are rendered to the page at the same time.
How can I render one filter whenever needed (by one or more components), and remove it afterwards?