Reputation: 20560
I'm building a CMS system for managing marketing landing pages. On the "Edit Landing Page" view, I want to be able to load the associated stylesheet for whichever landing page the user is editing. How could I do something like this with React?
My app is fully React, isomorphic, running on Koa. My basic component heirarchy for the page in question looks something like this:
App.jsx (has `<head>` tag)
└── Layout.jsx (dictates page structure, sidebars, etc.)
└── EditLandingPage.jsx (shows the landing page in edit mode)
Data for the landing page (including the path of the stylesheet to load) is fetched asynchronously in EditLandingPage
in ComponentDidMount
.
Let me know if you need any additional info. Would love to get this figured out!
Bonus: I'd also like to unload the stylesheet when navigating away from the page, which I assume I can do the reverse of whatever answer comes my way in ComponentWillUnmount
, right?
Upvotes: 41
Views: 97010
Reputation: 21
https://www.npmjs.com/package/react-helmet
Install react-helmet and use it for dynamic css for separate components. Example is For 1st component, using style1.css
<>
<Helmet>
<link rel="stylesheet" href="/css/style1.css" />
</Helmet>
...
</>
<>
<Helmet>
<link rel="stylesheet" href="/css/style1.css" />
</Helmet>
...
</>
Upvotes: 2
Reputation: 453
On my approach i use this:
const TenantSelector = ({ children }) => {
// imagine its value from a json config
const config = {
custom_style: 'css/tenant.css'
}
require(`./assets/${config.custom_style}`)
return (
<>
<React.Suspense fallback={<></>}>
</React.Suspense>
{children}
</>
)
}
ReactDOM.render(
<TenantSelector>
<YourApp>
</TenantSelector>,
document.getElementById("root")
)
Upvotes: 0
Reputation: 575
I use react-helmet, in render function....
{inject ?
<Helmet>
<link rel="stylesheet" href="css/style.css" />
</Helmet> : null}
Upvotes: 4
Reputation: 115
Instead of creating elements for stylesheet, you can also try importing your css based on some condition. ECMAScript provides a proposal that enables dynamic module imports, that works as follows:
if (condition) {
import('your css path here').then((condition) => {});
}
Upvotes: 0
Reputation: 605
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>
);
};
Upvotes: 21
Reputation: 2624
This is how I add style dynamically:
import React, { Component } from "react";
class MyComponent extends Component {
componentDidMount() {
const cssUrl = "/public/assets/css/style.css";
this.addStyle(cssUrl);
}
addStyle = url => {
const style = document.createElement("link");
style.href = url;
style.rel = "stylesheet";
style.async = true;
document.head.appendChild(style);
};
render() {
return <div> textInComponent </div>;
}
}
export default MyComponent;
Upvotes: 1
Reputation: 4064
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
Upvotes: 43
Reputation: 86220
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:
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.
Upvotes: 8