Skip to content

Instantly share code, notes, and snippets.

@Nitive
Created June 23, 2017 15:50
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 Nitive/f43309cbc206eaf285835c3929c8fce3 to your computer and use it in GitHub Desktop.
Save Nitive/f43309cbc206eaf285835c3929c8fce3 to your computer and use it in GitHub Desktop.
Icon component which load icons async on client but sync on server
import * as React from 'react'
// TLDR: This component load icons async on client but sync on server
export default class Icon extends React.PureComponent {
constructor(props) {
super(props)
// We should add DefinePlugin to webpack which replace
// process.env.IS_SERVER_SIDE with true or false.
// Then UglifyJS will remove require() in client code so icons won't be loaded.
// It's all we need to get icons in Server Side Rendering, the rest of
// component (expect render) is client code.
this.state = {
iconContent: process.env.IS_SERVER_SIDE
? require(`./icons/${props.name}.svg`)
: undefined,
}
}
componentWillMount() {
const isClientSide = typeof window !== 'undefined'
if (isClientSide) {
// Before component will mounted we should find it in DOM because
// it can already be there because we get html from server.
// If it is then put its html to state.
// We need do it in componentWillMount because we should get the same
// layout in first render to avoid layout mismatch and rerendering this
// component without content until it was loaded by import() in
// componentDidMount.
const node = document.getElementsByClassName(`js-icon-${this.props.name}`)[0]
if (node && node.innerHTML) {
// **TRICKY PART**: innerHTML changes self-closing tags into two tags:
// one open and one closed. To fix it use posthtml-loader with no plugins
// on icons. PostHTML will convert svg to according with standart
// just like .innerHTML.
this.state.iconContent = node.innerHTML
}
}
}
componentDidMount() {
// Skip loading icon if we already get its content.
if (!this.state.iconContent) {
// Load icon and put it to state on client
import('./icons/' + this.props.name + '.svg')
.then(content => {
this.setState({ iconContent: content })
})
.catch(err => console.log(err))
}
}
render() {
// Render nothing if icon not loaded yet
if (!this.state.iconContent) {
return null
}
return (
<span
// Add class which we use to find element in DOM
className={`js-icon-${this.props.name}`}
dangerouslySetInnerHTML={{ __html: this.state.iconContent }}
/>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment