Created
June 23, 2017 15:50
-
-
Save Nitive/f43309cbc206eaf285835c3929c8fce3 to your computer and use it in GitHub Desktop.
Icon component which load icons async on client but sync on server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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