Standalone React Component with Webpack - reactjs

I've got a component I'd like to share/reuse in some projects. I'm trying to build/bundle this component so it doesn't take the large setup that react normally does (webpack/babel/npm/ect).
I want to
Include React/ReactDOM from a cdn somewhere on an html page.
Include my component js file (we'll call it standalone.js).
Little bit of initialization code to render this into the dom. No Babel, No Webpack, No JSX.
That's all.
I feel like I've gotton pretty close, though am stuck on item 3. I cannot figure out how render my component to the DOM.
Here's the relevant part of demo html page:
index.html (relevant parts)
<div id="app" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js"></script>
<!--My Component -->
<script src="build/standalone.js" type="text/javascript"></script>
<script>
// I believe I'm doing something wrong here
var myComponent = new MyLibrary.default();
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
</script>
standalone.jsx
import React, {PropTypes} from 'react';
class Standalone extends React.Component {
render() {
return <p>{this.props.message}</p>;
}
}
Standalone.PropTypes = {
message: PropTypes.string.isRequired
}
export default Standalone;
webpack.config.js (relevant parts)
var config = {
entry: APP_DIR + '/standalone.jsx',
output: {
library: 'MyLibrary',
libraryTarget: 'umd',
path: BUILD_DIR,
filename: 'standalone.js'
},
module: {
loaders: [
{
test: /\.jsx?/,
include: APP_DIR,
loader: 'babel'
}
]
},
externals: {
react: 'React',
"react-dom": 'ReactDOM'
},
}
With trying to render the component with basic html I've tried a bunch of variations of similar things. Looking in my debugger, I can tell the object is something 'close' to a react-type object. I just don't know what to do with it.
Any pointers appreciated

You should not instantiate components with a new, rather they should be instantiated with React.createElement factory. So you just pass reference to the element class/function to createElement, see modified part of yout html:
...
// get reference to my component constructor
var myComponent = MyLibrary.default;
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
...
On a side note, to simplify debugging while in development (and only in development!) I suggest to use non minified version of react.js and react-dom.js, they are located under node_modules, for instance:
<script src="/node_modules/react/dist/react.js"></script>
<script src="/node_modules/react-dom/dist/react-dom.js"></script>

You may want to consider exposing your React component as a webcomponent, such as with https://www.npmjs.com/package/reactive-elements
<body>
<my-react-component item="{window.someValue}"></my-react-component>
</body>

Related

AngularJS template loading late. Directive not able to find the elements

I'm working on preparing a .net based AngularJS web application for modern tooling(get rid of nuget in favor of npm, bundling with webpack, etc.) and later on re-writing it to Angular.
I'm having an issue with the bundled version where a directive is trying to bind a click events to a template anchor tag (< a >) but the template is not yet loaded.
On the old version with many < script > tags for every JS file this is not happening. The directive is first on the order and the controller of the template(which is loaded inside a ng-include and uses the directive) comes after.
On the bundled version I simply changed the .js files to .ts, added the npm dependencies, the needed imports statements on each file and in the webpack entry I kept the same order as in the old index.html. Still, when de directive code runs the elements it searches for are not there yet.
The parts affected: (already updated with #bryan60 answer suggestion)
shell.html
...
<div data-ng-if="vm.showMenuBar" data-ng-include="'/app/layout/sidebar.html'" class="fader-animation"></div>
...
shell.ts
import angular from 'angular';
const sideBarTemplate = require('./sidebar.html')
let controllerId = "shell";
angular.module("eqc").controller(controllerId, ["$rootScope", "$templateCache", "$window", "authService", "common", "config", shell]);
function shell($rootScope, $templateCache, $window, authService, common, config) {
$templateCache.put('app/layout/sidebar.html', sideBarTemplate)
sidebar.html
<div data-cc-sidebar data-ng-controller="sidebar as vm">
<div class="sidebar-filler"></div>
<div class="sidebar-dropdown">Menu</div>
<div class="sidebar-inner">
<div class="sidebar-widget"></div>
<ul class="navi">
<li class="nlightblue fade-selection-animation" data-ng-class="vm.isCurrent(r)" data-ng-repeat="r in vm.navRoutes">
</li>
</ul>
</div>
cc-sidebar directive:
app.directive('ccSidebar', function () {
// Opens and clsoes the sidebar menu.
// Usage:
// <div data-cc-sidebar>
// Creates:
// <div data-cc-sidebar class="sidebar">
var directive = {
link: link,
restrict: 'A'
};
return directive;
function link(scope, element, attrs) {
var $sidebarInner = element.find('.sidebar-inner');
var $dropdownElement = element.find('.sidebar-dropdown a');
element.addClass('sidebar');
$dropdownElement.click(dropdown); // <--- Here's the problem. 'dropdown' function is omitted but its defined in the next line
// The error on the console is: TypeError: "$dropdownElement.click is not a function"
// That's because it is never found
main.ts
import "./eqc";
import "./config"
import "./config.exceptionHandler"
import "./config.route"
//All folders bellow have index.ts in them including their .ts files
import "./common"
import "./services"
import "./Views"
import "./layout"
eqc.ts
import 'jquery';
import angular from 'angular';
import 'angular-animate';
import 'angular-route';
import 'angular-sanitize';
import 'angular-messages';
import 'angular-local-storage'
import 'angular-ui-bootstrap';
import 'angular-ui-mask';
import 'angular-loading-bar';
import 'breeze-client';
import 'breeze-client/breeze.bridge.angular';
let eqc = angular.module("eqc", [ .....
webpack.config.ts
const path = require('path');
module.exports = {
mode: "development",
entry: "./src/app/main.ts",
output: {
path: path.resolve(__dirname, "src/dist"),
filename: "bundle.js"
},
module: {
rules: [
//All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
{
test: /\.tsx?$/,
use: { loader: "ts-loader" }
},
{
test: /\.html$/,
use: { loader: 'html-loader' }
}
]
},
resolve: {
//Add '.ts' and '.tsx' as a resovaable extension
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
devtool: "source-map"
};
My folder structure:
not sure exactly what your problem is due to the lack of code / details... but very generally, the simplest way to make ng-include work with webpack is to use require statements and the template cache....
Assuming you have some template like:
<ng-include src="'app/my-included-template.html'"></ng-include>
in the controller for that template, you'll have something set up like...
const myIncludedTemplate = require('./my-included-template.html')
function MyController($templateCache) {
$templateCache.put('app/my-included-template.html', myIncludedTemplate)
}
to make the require statement work with webpack, you'll need am html loader configured, i have this to do it in my module rules array...
{
test: /\.html$/,
use: [
'html-loader'
],
},
this particular implementation will require you to npm install --save-dev html-loader
this will instruct webpack to inline your template to the source file, and then your controller will immediately put it into the template cache so that it can be loaded correctly without worrying about the webpack bundle structure itself as angular will check the template cache before loading remotely. This is also a generally more performant way of doing things.
you can also do something similar when defining your directive / component templates:
template: require('./my-directive-template.html'),
which yields the same benefits of webpack just inlining the template so it doesn't need to be loaded remotely.

React JSX class library exports are not working

I'm in the early stages of moving a non-jsx React project to JSX and struggling a bit to get it working.
I've managed to create a simple JSX component working when created from a, index.jsx file, but I'm trying to integate with existing code, and I'd like to start by combining 'legacy' React classes with my new JSX classes.
My Dropdown.jsx code looks like this:
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null,
};
}
render() {
return <div>
<select name="carlist" form="carform">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="opel">Opel</option>
<option value="audi">Audi</option>
</select>
</div>;
}
}
export default Dropdown;
The html file I'm trying to instantiate the React component from is as follows (this is how most of my legacy code looks )
<!DOCTYPE html>
<html lang="en" xmlns="">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script>
<script src="dist/react-bundle.js"></script>
<script type="application/javascript">
ReactDOM.render(
React.createElement(Dropdown, {}, null),
document.getElementById('app')
);
</script>
</body>
</html>`
And my webpack.config.js file is as follows:
var path = require('path') ;
module.exports = {
entry: {
react: [ './src/Dropdown.jsx' ]
},
output: {
library: "Dropdown",
libraryTarget: "umd",
path: path.join( __dirname, 'dist' ),
filename: '[name]-bundle.js'
},
devtool: 'source-map',
module: {
loaders:
[
{
test: /\.js$/,
exclude: /node_modules/,
loaders: [
'babel-loader'
]
},
{
test: /\.jsx$/,
exclude: /node_modules/,
loaders: [
'babel-loader'
]
}
]
}
};
I've done a fair bit of research to try to resolve this, the last of which was adding the 'library' and 'libraryTarget' settings to the config file.
The script SEEMS to be recognising the Dropdown class ( I.e. it's not failing with a class not found type error ) but I'm getting the folloiwng instead:
Warning: React.createElement: type should not be null, undefined, boolean, or number.
It should be a string (for DOM elements) or a ReactClass (for composite components).
I know this class works when instantiated from an index.jsx file
import React from 'react';
import ReactDOM from 'react-dom';
import Dropdown from './Dropdown';
ReactDOM.render(
<Dropdown />,
document.getElementById('app')
);
export default Dropdown ;
Am I approaching this migration in the wrong way? - I would have thought I could mix and match old React code with JSX output...?
Many thanks in advance
The thing with Webpack is that the classes you have created are properly encapsulated from the outside world. the error you are getting
Warning: React.createElement: type should not be null, undefined, boolean, or number.
It should be a string (for DOM elements) or a ReactClass (for composite components).
is an error with react when React cannot find the class declaration anywhere. while I would suggest you follow the proper way and instantiate it in an index.jsx file, you can still make that work by doing this hack in Dropdown.jsx
window.Dropdown = Dropdown
this would make sure that your external non webpack compiled javascript can see the dropdown class. but this is bad pactice and I would really suggest you stick with the index.jsx file as it protects you from exposing your classes and libraries from the global namespace and its a best practice.

How to use react as a component library rather than application

I'm trying to learn react and also introduce it in already existing project. Thus I want to use react in a few places where it makes sense, instead of using it everywhere.
The problem is the entry-point concept that I can't wrap my head around. I'd prefer to have:
ReactDOM.render(
<MyComponent/>,
document.getElementById("componentExample")
);
inside my cshtml near <div id='componentExample'></div> rather than in a separate jsx. I understand that I can't use jsx syntax in cshtml, but if I write
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(MyComponent, {}, null), document.getElementById("componentExample")
);
</script>
This doesn't really work because MyComponent is undefined. It is included in my bundle.js (I had to add it as entry point), but it is inside the scope of some webpack stuff and is not exposed.
What is the proper way of doing this?
My goal is to write some components for my app where it is more suitable and use it here and there, without completely going into react-based architecture.
My setup is webpack + babel polyfill + typescript + react and this is in ASP.NET MVC.
My webpack.config.js is:
var config = {
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx", "./SiteScripts/React/Components/MyComponent.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
rules: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
enforce: 'pre',
test: /\.js$/,
loader: "source-map-loader"
},
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
{
test: /\.tsx?$/, loaders: ['babel-loader', "ts-loader"], exclude: /node_modules/
}
]
},
plugins : []
}
if (process.env.NODE_ENV === "production") {
config.devtool = "cheap-module-source-map";
}
module.exports = config;
Also this is somewhat related to the question (as question about architecture): I'd like to make my bundle.js smaller - it is 30k lines now. It is probably because I'm trying to use react in a wrong way?
I finally found the problem. I had a few mistakes (and didn't completely understand the whole theory behind webpack) and the answer is definetely beyound the code I gave in the question - so I do not have any answers.
First I had to make by bundle a library, in order to export stuff defined in it (webpack.config.js):
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
libraryTarget: "var",
library: "WebPack"
},
This will make types/variables defined in index.tsx (I think only last file from entry list is taken) and that are exported - available via WebPack.<name>
I needed to export my component. I've done it like this - but there are probably better ways (index.tsx):
import { MyComponent} from "./Components/mycomponent";
export var myComponent = MyComponent;
I also needed to ensure all my subcomponents are modules. This means using import everywhere, and adding a keyword export to all element, props and states.
After that I was able to render my component from:
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(WebPack.myComponent,
{
PropA = 2, PropB = "3"
}, null),
document.getElementById("componentExample")
);
</script>
One more thing to watch for if I am doing this is that the line <script src="#... does not happen more than once in my result html (this is very likely as I am using many components embedded in a single page). I use this javascript function to ensure I load bundle.js only once:
function loadJsOnce(url, whenFinish) {
if (!window.loadedScripts) {
window.loadedScripts = {};
}
if (!window.loadedScripts[url]) {
var script = document.createElement('script');
//script.async = true; // remove this if you don't want the script to be async
script.src = url;
script.onload = function() {
whenFinish();
}
document.getElementsByTagName('head')[0].appendChild(script);
window.loadedScripts[url] = true;
} else {
whenFinish();
}
}
In url I specify the path to bundle.js and in whenFinish function I put a function that calls ReactDOM.render(... stuff

How can I render the react app with redux using the ReactJS.NET?

I have created the Single page application where I have used the React, Redux and React-Router.
I want to render this SPA using ReactJS.NET for improve perfomance of loading the application.
The SPA is compiled as one Bundle.js using Webpack.
I have an ASP.NET MVC application where I can paste this bundle which will be rendered through the ReactJS.NET. Everything in this SPA will be worked in one ASP.NET view but with the React-router.
How can I do that? I can't find any project where is solved this combination with redux.
Thank you for the example or help.
I know this is an old question but if you're still having issues incorporating your React app into React.NET you should try the following template and have a look at how this fellow has done it.
He uses webpack to first build and compile a server specific set of code then pulls the compiled js into React.NET
app.UseReact(config =>
{
config
.SetLoadBabel(false)
.AddScriptWithoutTransform("~/js/react.server.bundle.js");
});
The webpack config looks like this.
var path = require('path');
var webpack = require('webpack');
var WebpackNotifierPlugin = require('webpack-notifier');
module.exports = {
entry: {
server: './React/server.jsx',
client: './React/client.jsx',
clientWithRender: './React/clientWithRender.jsx',
},
output: { path: __dirname + '/wwwroot/js/', filename: 'react.[name].bundle.js' },
module: {
loaders: [
{
test: /.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy']
}
}
]
},
plugins: [
new WebpackNotifierPlugin()
]
};
And heres the index
#{
Layout = null;
}
<html>
<head>
<title>Hello React</title>
</head>
<body>
<div id="app">
#{
if (Environment.GetEnvironmentVariables()["ASPNETCORE_ENVIRONMENT"].ToString() != "Development")
{
#Html.React("ReactComponents.App", new { val1 = "Top text" });
}
}
</div>
#{
if (Environment.GetEnvironmentVariables()["ASPNETCORE_ENVIRONMENT"].ToString() != "Development")
{
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.min.js"></script>
<script src="#Url.Content("~/js/react.client.bundle.js")"></script>
#Html.ReactInitJavaScript()
}
else
{
<script src="#Url.Content("~/js/react.clientWithRender.bundle.js")"></script>
}
}
</body>
</html>
Why use ReactJS.NET? There is nothing special about an MVC.NET stack which requires a separate project to get React up and running in it. I would use a combination of Babel, Webpack, React, React-Dom and React-Router instead. MVC.NET should just deliver the bundle everything else should be react and it's dependencies.
The problem with the default tutorial of React.NET is that it does not consider the fact you are using webpack to bundle your dependencies and instead has examples of adding them manually. This is not really the preferred way of writing React and makes a complicated process even more complicated by trying to hide away the initial complexity of setting up your React project.
Suggestion:
Webpack will bundle your react-router, react and react-dom amongst other stuff. You need MVC to be setup in a way that every url request is handled by the same controller action that way React-Router can handle the url changes. This answer explains how to do this. ASP.NET MVC - Catch All Route And Default Route
Without doing this, MVC will try to handle all url route changes instead of React-Router doing it's thing.

ngReact can't find my react component

I've been trying to get this to work for ages, but no matter what I do the directive can't find my react component.
These are the files I'm including:
<script src="bower_components/react/JSXTransformer.js"></script>
<script src="bower_components/react/react-with-addons.js"></script>
<script src="bower_components/ngReact/ngReact.min.js"></script>
<script src="node_modules/babel-core/browser.js"></script>
<script src="static/react-components.min.js" type="text/babel"></script>
<script src="static/main.min.js"></script>
Where my components are inside the react-components.min.js file, and all of my angular code is inside main.min.js.
It's stated that you (might(?)) need to use an in browser transformator for this directive to work, so I tried that using babel, but that also doesn't work.
This is my react component:
<react-component name="Chat" watch-depth="reference"></react-component>
And in the react-components.min.js file I got a component called 'Chat':
var Chat = React.createClass({
render: function() {
return <footer className="chat chat--dark">Hello John</footer>;
}
});
core.value('Chat', Chat); // My application is bound to the core module
But it doesn't find it.. What could be wrong because no one else seems to have this issue?
JSX Compilation Issue
I believe it is a compilation error. I ran your code through an online compiler (https://babeljs.io/repl/) swap your current component code out with the block below and see if it works:
"use strict";
var Chat = React.createClass({
displayName: "Chat",
render: function render() {
return React.createElement(
"footer",
{ className: "chat chat--dark" },
"Hello John"
);
}
});
core.value('Chat', Chat); // My application is bound to the core module
JSXTransformer is deprecated you should add in a build step that uses Babel.
https://facebook.github.io/react/blog/2015/06/12/deprecating-jstransform-and-react-tools.html
Check out my blog post for the breakdown on adding JSX and ES6 support.
http://blog.tylerbuchea.com/migrating-angular-project-to-react/

Resources