SVG Loader
SVG Loader
by Csaba Hellinger
HTML
<div id="app"></div>
CSS
body {
fill: blue;
}
.container {
display: flex;
gap: 5px;
height: 40px;
border: 1px solid red;
}
React
console.clear();
const mockFetch = (url) => {
console.log('fetch', url);
return fetch('https://upload.wikimedia.org/wikipedia/commons/0/0b/Gear_icon_svg.svg');
};
// ---------- IconProvider.jsx ----------
// Common base URL (represents an icon set), fetching and caching for all icons.
const IconContext = React.createContext(null);
const IconProvider = ({ baseUrl, children }) => {
const cacheRef = React.useRef({});
const getSvg = async (name) => {
if (name in cacheRef.current) {
return cacheRef.current[name];
}
const url = `${baseUrl}/${name}.svg`;
// put the top promise into the cache immediately.
// if we await, multiple Icons fire multiple fetches by the time the first promise resolves.
cacheRef.current[name] = mockFetch(url)
.then(async res => {
if (!res.ok) {
console.warn('svg not found', url)
return null;
}
const svg = await res.text();
// TODO: sanitize
// TODO: remove svg width & height in a generic way
return svg.replace(`width="280px" height="279.416px"`, 'width="100%" height="100%"');
});
return cacheRef.current[name];
}
const context = { getSvg };
return <IconContext.Provider value={context} children={children} />;
};
// ---------- Icon.jsx ----------
// Render one icon using the fetcher from context. Only prop needed is the icon name.
const Icon = ({ name }) => {
const context = React.useContext(IconContext);
const [svg, setSvg] = React.useState();
context.getSvg(name).then(svg => {
setSvg(svg);
});
return <span
dangerouslySetInnerHTML={{ __html: svg }}
style={{ display: 'inline-block' }}
/>;
}
// ---------- App.jsx ----------
ReactDOM.render(
<IconProvider baseUrl="https://foo.com/icon-set-1">
<h3>Dynamically loaded styleable external SVGs</h3>
<div class="container">
<Icon name="cog" />
<Icon name="cog" />
</div>
</IconProvider>,
...