Dynamically load a stylesheet with React

爷,独闯天下 提交于 2019-11-28 04:25:14

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!