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>, 
 ...