Dynamically load a stylesheet with React
Just update stylesheet's path that you want to be dynamically loaded by using react's state.
import * as React from 'react';export default class MainPage extends React.Component{ constructor(props){ super(props); this.state = {stylePath: 'style1.css'}; } handleButtonClick(){ this.setState({stylePath: 'style2.css'}); } render(){ return ( <div> <link rel="stylesheet" type="text/css" href={this.state.stylePath} /> <button type="button" onClick={this.handleButtonClick.bind(this)}>Click to update stylesheet</button> </div> ) }};
Also, I have implemented it as react component. You can install via npm install react-dynamic-style-loader.
Check my github repository to examine:
https://github.com/burakhanalkan/react-dynamic-style-loader
This is prime mixin teritority. First we'll define a helper to manage style sheets.
We need a function that loads a style sheet, and returns a promise for its success. Style sheets are actually pretty insane to detect load on...
function loadStyleSheet(url){ var sheet = document.createElement('link'); sheet.rel = 'stylesheet'; sheet.href = url; sheet.type = 'text/css'; document.head.appendChild(sheet); var _timer; // TODO: handle failure return new Promise(function(resolve){ sheet.onload = resolve; sheet.addEventListener('load', resolve); sheet.onreadystatechange = function(){ if (sheet.readyState === 'loaded' || sheet.readyState === 'complete') { resolve(); } }; _timer = setInterval(function(){ try { for (var i=0; i<document.styleSheets.length; i++) { if (document.styleSheets[i].href === sheet.href) resolve(); } catch(e) { /* the stylesheet wasn't loaded */ } } }, 250); }) .then(function(){ clearInterval(_timer); return link; });}
Well $#!@... I was expecting to just stick an onload on it, but nope. This is untested, so please update it if there are any bugs – it's compiled from several blog articles.
The rest is fairly straight forward:
- allow loading a stylesheet
- update state when it's available (to prevent FOUC)
- unload any loaded stylesheets when the component unmounts
- handle all the async goodness
var mixin = { componentWillMount: function(){ this._stylesheetPromises = []; }, loadStyleSheet: function(name, url){ this._stylesheetPromises.push(loadStyleSheet(url)) .then(function(link){ var update = {}; update[name] = true; this.setState(update); }.bind(this)); }, componentWillUnmount: function(){ this._stylesheetPromises.forEach(function(p){ // we use the promises because unmount before the download finishes is possible p.then(function(link){ // guard against it being otherwise removed if (link.parentNode) link.parentNode.removeChild(link); }); }); }};
Again, untested, please update this if there are any issues.
Now we have the component.
React.createClass({ getInitialState: function(){ return {foo: false}; }, componentDidMount: function(){ this.loadStyleSheet('foo', '/css/views/foo.css'); }, render: function(){ if (!this.state.foo) { return <div /> } // return conent that depends on styles }});
The only remaining todo is checking if the style sheet already exists before trying to load it. Hopefully this at least gets you on the right path.
I think that Burakhan answer is correct but it is weird to load <Link href = "" />
inside the body tag. That's why I think it should be modified to the following [ I use React hooks]:
import * as React from 'react';export default MainPage = (props) => { const [ stylePath, setStylePath ] = useState("style1.css"); const handleButtonClick = () => { setStylePath({stylePath: 'style2.css'}); } useEffect(() => { var head = document.head; var link = document.createElement("link"); link.type = "text/css"; link.rel = "stylesheet"; link.href = stylePath; head.appendChild(link); return () => { head.removeChild(link); } }, [stylePath]); return ( <div> <button type="button" onClick={handleButtonClick}> Click to update stylesheet </button> </div> );};