How to load css provided by webpack inside an IFrame created using "react-frame-component" library? - reactjs

I'm trying to render my React component inside an iframe. I got it to work by using the library "react-frame-component". The problem is that the styles are loaded outside the iframe, at the end of "head" element. I want to change it to be loaded inside the "head" of "iframe" element.
I'm using Webpack to generate the bundles with JS and CSS included and I saw that I can change where "style-loader" will load the styles by setting the option "insertInto", but this is throwing an error:
Uncaught Error: Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid.
This is my React component:
import Frame from 'react-frame-component'
...
ReactDOM.render(
<Frame id="someId">
<Provider store={Store.get()}>
<Container/>
</Provider>,
</Frame>,
document.body
);
This is my "style-loader" in Webpack configuration:
{
loader: 'css-loader',
options: {
insertInto: () => document.querySelector("#someId"),
},
}
I think the problem is that the component was not rendered when webpack tried to include the styles. How to solve this?

You would need to load the styles into a temporary element as placeholder, and then use Javascript to clone those styles into the new frame.
// index.html
...
<div id="dynamic-styles"></div>
...
// webpack.config.js
...
{
loader: 'css-loader',
options: {
insertInto: () => document.getElementById("dynamic-styles"),
},
}
...
// some.js
...
const stylesContainer = document.getElementById('dynamic-styles');
const frame = document.getElementById('someID');
const frameHead = frame.contentWindow.document.head;
[...stylesContainer.children].forEach(styleNode => {
const newStyle = document.createElement('style');
newStyle.textContent = styleNode.textContent;
frameHead.appendChild(newStyle);
});
I haven't tested the above snippet but I know the concept works. I feel it gets the point across.
There may be a better way to do this but I haven't looked into it enough to figure it out yet. It'd be nice to be able to get webpack to make separate style bundles so that each configured frame could just automatically lazy load it's own dedicated bundled css

Related

Dynamically changing Less variables in React Gatsby app with Ant Design at runtime

We are building a White Label platform using React, GatsbyJs and Ant Design. We are stuck with Gatsby and Ant Design because we are migrating from an existing system and changing any of those would bring huge impact. Also, we must have a single deploy. Having a build for each White Label is not an option.
So, we need to be able to change style (mainly color) at runtime.
The problem is: Ant Design uses less variables to define it's themes and we're not able to change them at runtime, not even with less's modifyVars.
The thing is we MUST change less variables, and not global CSS or use other means
Ant Design derivates the main variables many times to get adjacent properties. So, for instance, if we define #primary-color as red, when we add a Button to the screen, Ant Design also defines it's border color, hover color, and many other details with different shades of red.
This means that, if we were to use other styling tool, we would need to generate those color derivations and replace every little property for every component. This would be chaos.
Scenario
We are using gatsby-plugin-antd and gatsby-plugin-less to load less and change vars at build time. Our gatsby-config.js looks like this:
module.exports = {
siteMetadata: {
siteUrl: 'https://www.yourdomain.tld',
title: 'yourtitle'
},
plugins: [
'gatsby-plugin-root-import',
'gatsby-plugin-typescript',
{
resolve: 'gatsby-plugin-antd',
options: {
style: true
}
},
{
resolve: 'gatsby-plugin-less',
options: {
lessOptions: {
javascriptEnabled: true,
modifyVars: {
'primary-color': '#FFFFFF',
'link-color': '#000000',
'success-color': '#FFFFFF',
'warning-color': '#000000'
}
}
}
}
]
};
We import styling in our gatsby-browser.js file:
import './src/styles/index';
Our styles/index has:
import 'tachyons';
import './global.css';
import './antd.less';
antd.less:
#import '~antd/dist/antd.less';
And global.css has some general CSS for the project.
It's working fine with the defined variables at build time.
What we attempted so far...
We have tried out this plugin:
https://github.com/mzohaibqc/antd-theme-webpack-plugin
Which supposedly does exactly what we need. But there's no example using Gatsby.
We then tried to add the plugin using the gatsby-node.js as mentioned here:
https://www.gatsbyjs.com/docs/how-to/custom-configuration/add-custom-webpack-config/
First, we tried using index.html as the indexFileName for the pluggin. It just doesn't work.
Then, following the plugin docs, we tried using indexFileName as false and importing the following scripts using Helmet at our pages/index.tsx:
<script> window.less = { async: false, env: 'production' };
</script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script> ```
Also didn't work. If we define indexFileName as false, we get memory over the heap.
If we keep indexFileName as 'index.html' and just add the scripts, we are able to call window.less.modifyVars and it returns successfully (we are logging the Promise's then and error) but it doesn't affect antd's variables.
We then tried doing something similar, but instead of loading less externally, we installed it as a node_module and imported it to the file and used it directly in a similar fashion. Got the same result: modifyVars runs and returns successfully but doesn't affect antd.
Then, we tried something a bit different: we removed gatsby plugins and tried importing less from antd directly, as suggested here:
https://ant.design/docs/react/customize-theme
So we imported it like this:
#import '~antd/lib/style/themes/default.less';
#import '~antd/dist/antd.less';
#import 'your-theme-file.less';
Also, no good. It's different from the previous scenario, because style gets updated after you save your code. No need to stop Gatsby, as the first solutions. But, modifyVars still has no affect on antd components.
Then, to isolate the issue, we tried to style a basic HTML component - a button - to check if the issue was with gatsby or antd. And... still no success. less.modifyVars didn't work to change a basic button style on runtime.
So, we think it's probably something between Gatsby and Less. We checked gatsby-plugin-antd and gatsby-plugin-less to see if we could find something, but found nothing useful.
We assume that the "less instance" or "less context" used by gatsby's less-loader during build time is not the same we are calling modifyVars on. So it doesn't affect the original vars.
Totally stuck. Please, help!
EDIT - SOLUTION
Ant Design team has just released - TODAY - a new alpha version that includes dynamic theming, using CSS Variables.
https://ant.design/docs/react/customize-theme-variable
It works fine, so far. Closing the issue.
EDIT 2
There's a more detailed solution on the accepted answer.
Ant Design team has just released - TODAY - a new alpha version that includes dynamic theming, using CSS Variables.
https://ant.design/docs/react/customize-theme-variable
It works fine, so far.
EDIT - Detailed solution
I removed gatsby-plugin-antd and gatsby-plugin-less from the project. Also removed the import of antd less file.
Instead, in my styles/index.tsx (which is imported in gatsby-browser.js), I'm importing the variables.min.css file:
import 'antd/dist/antd.variable.min.css';
Then, whenever I want to change Ant Design variables, I just use:
import { ConfigProvider } from 'antd';
...
ConfigProvider.config({
theme: {
primaryColor: '#[DESIRED_COLOR_HEX]'
}
});
Provider
Since this has to be done every time the site is loaded, I'm creating a ThemeProvider that wraps every page and defines the theme. It fetches theme data from the backend and sets Ant Design theme variables.
Example code:
import { Spin } from 'antd';
import React, { useEffect, useState } from 'react';
import { ConfigProvider } from 'antd';
import { Theme } from './theme.interface';
interface Props {
children: React.ReactNode;
}
export const ThemeProvider = ({ children }: Props): JSX.Element => {
const [themeVars, setThemeVars] = useState<Theme>(null);
useEffect(() => {
async function fetchMyAPI() {
const result = await getThemeFromBackend(); // Make API call with Axios
if (result) setThemeVars(result);
}
fetchMyAPI();
}, []);
useEffect(() => {
if (themeVars) {
ConfigProvider.config({
theme: {
primaryColor: `#${themeVars.primaryColor}`
}
});
}
}, [themeVars]);
return <div>{!themeVars ? <Spin size="large" /> : <>{children}</>}</div>;
};
And it can be used like this:
...
<ThemeProvider>
<h1>My page header</h1>
<p>Page content...</p>
</ThemeProvider>
...
Note: You can save theme data on local storage for performance improvement, so you don't have to call your backend everytime your site reloads. Maybe you'll just have to refresh it from time to time.

How to Use SVG with React and ReasonML?

With create-react-app and JavaScript/TypeScript, I understand I'm able to "import" an SVG as noted below. How may I do so with ReasonML?
import { ReactComponent as Logo } from './logo.svg';
function App() {
return (
<div>
{/* Logo is an actual React component */}
<Logo />
</div>
);
}
Create React App uses webpack to transform SVG files into React components. If you’re using Reason with CRA, then all you need to do is provide a binding to the generated component. However, CRA will only transform the SVG into a component if the import statement is written exactly a certain way, which isn't how BuckleScript outputs import statements. (There's a GitHub issue about it here.) You have to import it with raw JavaScript and then bind to the imported value:
%bs.raw
{|import {ReactComponent as _Logo} from "./logo.svg"|};
module Logo = {
[#react.component] [#bs.val] external make: unit => React.element = "_Logo";
};
/* And we can use it like a regular component: */
[#react.component]
let make = () =>
<div>
<Logo />
</div>;
According to the CRA docs:
The imported SVG React Component accepts a title prop along with other props that a svg element accepts.
For any of the other props you want to use, you'll have to add them to your external binding.
If you're not using CRA, then you'll need to configure your bundler to do the same transformation. I'm not familiar with the internals of CRA, but this seems to be the relevant code from its webpack configuration.
We can use SVGR to handle the webpack loading and then import the module as we normally would.
const webpack = require('webpack');
module.exports = {
entry: './src/index.js',
module: {
rules: [
//...
{
test: /\.svg$/,
use: ['#svgr/webpack'],
},
],
},
//...
};
module Logo = {
#bs.module(`../../../../src/img/logo.svg`) #react.component
external make: {.} => React.element = "default"
}
...
<Logo /> // works
source: https://blog.logrocket.com/how-to-use-svgs-in-react/
I am the author of another solution that doesn't involve webpack.
It's a tool that can transform your svg files directly into .re files: https://github.com/MoOx/react-from-svg
This can create files for react (dom) or react-native(-web) (=> files generated use react-native-svg).
Feel free to try it :)
For example you can do (when the tool is installed from npm)
$ react-from-svg src/SVGs src/SVGs/components --with-native-for-reason --remove-fill
This will turns the files from svg/SVGs into React components into src/SVGs/components compatible for React Native with the Reason syntax.
The last option remove all svg fill props so you can use them as icons.
Note that the generated components accept width, height & fill props so you can adjust them when used.
Last bonus: since webpack is not involved, you can use this transformation only when you update your SVGs files & use this code directly with a Node runtime (JSX from Reason gets removed when converted to JS so the code can be consumed directly via Node without any transformation - which can be handy for tiny static sites/pages).

Loading Sass and injecting CSS text into a parent document from a React app

I have a React app that's loaded into a parent document via some shim JavaScript that:
creates an <iframe> element
creates a <div>
injects a <style> tag into the <head> in order to style the inserted <div>
Roughly, this works using the below:
// example.jsx
// Require the css using css-loader
const styles = require('../styles/example.css');
// Find the parent document
const doc = window.parent.document;
// Inject our stylesheet for the widget into the parent document's
// HEAD as a style tag
const css = styles.toString();
const style = doc.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
// This is required for IE8 and below.
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
doc.head.appendChild(style);
This uses css-loader in our Webpack config in order to have the require().toString() work for setting the cssText dynamically.
While this works, I don't know if this ideal. And we'd prefer to write Sass and get the benefits of #import to bring in other CSS (like resets), and get the benefits other tooling for Sass.
Is there a better way that we can achieve the same result of injecting <style> text into this parent document without sacrificing our ability to use the tooling we prefer?
Install react-helmet and then try this in your react component:
<Helmet>
<style>
// Insert CSS string here
</style>
</Helmet>
The other option is to load the sass directly in the parent but gate all of its styling behind a sass function that checks if #react-root exists.

Can't pass relative img src route in React + Webpack

I can't dynamically pass relative img src route in React + Webpack with this.props.
This is a very simplified part of the parent component. It iterates some data list and generates ImgComponents passing props. Here is only tiny part of it:
return (
<div>
<ImgComponent srcProp={videolist.anotherImage.src} />
</div>
);
The ImgComponent has:
render() {
return (
<div>
<img src={`${this.props.srcProp}`} />
</div>
);
}
In console I get exactly the same route as i pass with this.props.srcProp, but it would not find the folder and the img there. As if webpack does not load the img.
Here is the console:
<img src="framework/assets/images/cover-bigbuckbunny.png" class="_2WcF9ZmcK-9bQX6jKk18Y_" data-reactid=".0.1.1:$comedy.0.0.$0.0.0.0">
No matter what route i indicate, the img is never found in the folder.
Here is the webpack config for png:
{ test: /\.png$/, loader: "url-loader?mimetype=image/png" }
BTW. It works fine if i require the file, like:
<img src={require ('framework/assets/images/cover-bigbuckbunny.png')} />
or indicate some remote route, like http://route.to.some.img
Seems like webpack does not load it. What should i do to dynamically load local img files by dynamically passing src with {this.props.src} ? As I can see, the loader loads img fine if i indicate the file with require. But this way i cannot do it dynamically, as require does not take variables.
What I managed to do is to require('../../assets/images/myFirst.img') in the object, containing the routs to the locally stored files:
in playlist.js
export default (
{
cartoons: [{
images: {
poster: require('../../assets/images/myFirst.png')
},
streams: [{
type: 'mp4',
url: 'http://media.w3.org/2010/05/bunny/movie.mp4'
}]
});
Then I work with this list as with any object:
import playlist from '../../playlist';
and iterate this object in a usual manner. The image will be there.
Webpack will not traverse your code to find and convert all possible references to files.
Consider following code:
<img src={'/foo/bar/baz.jpg'} />
It is your responsibility (perhaps using a build target) to ensure that baz.jpg is copied into the foo/bar directory under your web app root.

this is undefined in Babel-compiled methods

I'm trying to write a small webapp with React 0.13 and ES6 syntax. I'm using webpack and babel-loader to compile:
loaders: [
{ test: /\.js/, exclude: /node_modules/, loader: "babel-loader"}
]
I'm having trouble with the this variable inside methods, getting "this is undefined" in several places throughout my code. For example:
export class PanelEditor extends React.Component {
...
update (){
if (!this.isMounted())
return;
this.setState(this.getStateFromStore());
}
...
}
The this variable should never be undefined under this conditions. However, I found that the problem might be the way Babel rewrites the code:
update: {
value: function update() {
if (!this.isMounted()) {
return;
}
this.setState(this.getStateFromStore());
}
},
In that way, it seems to me, the this variable refers to the object literal instead of the class. How can I fix this?
That's not actually the problem. It's that this is undefined, because you're not binding the update function.
You can do this in the constructor or in render. Most people do it in render.
<div onClick={this.update.bind(this)} />
Or (my preference), an arrow function which preserves this.
<div onClick={() => this.update()} />

Resources