Skip to content

Instantly share code, notes, and snippets.

@wmertens
Created January 27, 2018 16:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wmertens/37c63d8a7088b2bfa3af298fe49a6c98 to your computer and use it in GitHub Desktop.
Save wmertens/37c63d8a7088b2bfa3af298fe49a6c98 to your computer and use it in GitHub Desktop.
Use svg symbols in React, works without changes with SSR
import arrow from 'symbol-loader!arrow.svg'
const App = () => (
<SymbolProvider>
<svg><Use symbol={arrow} /></svg>
</SymbolProvider>
)
'use strict'
const path = require('path')
const SVGCompiler = require('svg-baker')
const svgCompiler = new SVGCompiler()
const getFileId = filePath => path.parse(filePath).name
module.exports = function(content) {
const resourceQuery = this.resourceQuery || ''
const done = this.async()
const id = getFileId(this.resourcePath)
svgCompiler
.addSymbol({id, content, path: this.resourcePath + resourceQuery})
.then(symbol => {
const wrappedId = JSON.stringify(symbol.id)
const text = JSON.stringify(symbol.render())
done(null, `module.exports = {id: ${wrappedId}, text: ${text}}`)
})
.catch(done)
}
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import Symbols from 'plugins/icons/Symbols'
class SymbolProvider extends Component {
static childContextTypes = {
icons: PropTypes.object,
}
uses = {}
childCtx = {
icons: {
register: fn => (this.onUpdate = fn),
unRegister: fn => {
if (this.onUpdate === fn) this.onUpdate = null
},
addSymbol: symbol => {
const ref = this.uses[symbol.id]
if (ref) {
if (ref.symbol !== symbol) {
console.error(new Error(`Symbol ID ${symbol.id} has to be unique`))
return
}
ref.count++
} else {
this.uses[symbol.id] = {symbol, count: 1}
}
if (this.onUpdate) this.onUpdate()
},
removeSymbol: symbol => {
const ref = this.uses[symbol.id]
if (!ref) {
console.log(new Error(`refcount error for symbol ${symbol.id}`))
return
}
ref.count--
if (ref.count < 1) delete this.uses[symbol.id]
},
getSymbols: () =>
Object.values(this.uses)
.map(s => s.symbol.text)
.join(''),
},
}
getChildContext() {
return this.childCtx
}
render() {
const {children} = this.props
return (
<React.Fragment>
{children}
<Symbols />
</React.Fragment>
)
}
}
export default SymbolProvider
import React, {Component} from 'react'
import PropTypes from 'prop-types'
class SvgSymbols extends Component {
static contextTypes = {
icons: PropTypes.object,
}
update = () => this.forceUpdate()
componentDidMount() {
this.context.icons.register(this.update)
}
componentWillUnmount() {
this.context.icons.unregister(this.update)
}
render() {
const {getSymbols} = this.context.icons
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
dangerouslySetInnerHTML={{__html: getSymbols()}}
/>
)
}
}
export default SvgSymbols
import React, {Component} from 'react'
import PropTypes from 'prop-types'
class Use extends Component {
static contextTypes = {
icons: PropTypes.object,
}
updateSymbols(oldSymbol, newSymbol) {
if (oldSymbol === newSymbol) return
const {addSymbol, removeSymbol} = this.context.icons
if (oldSymbol) removeSymbol(oldSymbol)
if (newSymbol) addSymbol(newSymbol)
}
componentWillMount() {
this.updateSymbols(null, this.props.symbol)
}
componentWillReceiveProps(nextProps) {
this.updateSymbols(this.props.symbol, nextProps.symbol)
}
componentWillUnmount() {
this.updateSymbols(this.props.symbol, null)
}
render() {
const {symbol} = this.props
if (!symbol) return false
return <use xlinkHref={'#' + symbol.id} />
}
}
export default Use
@wmertens
Copy link
Author

(Github doesn't notify for comments on gists, if you want to tell me something, mail me at Wout.Mertens@gmail.com)

@wmertens
Copy link
Author

This works because:

  • <use> can refer to symbols that follow it in the page
  • the SSR first renders the entire page, so by the time it renders the <Symbols/>, all the symbols used in the page are known

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment