Async loading script within react component - reactjs

I have a problem loading external script within react JSX
<script>
(function(d) {
var config = {
kitId: 1234567,
scriptTimeout: 3000,
async: true
},
h=d.documentElement,t=setTimeout(function(){h.className=h.className.replace(/\bwf-loading\b/g,"")+" wf-inactive";},config.scriptTimeout),tk=d.createElement("script"),f=false,s=d.getElementsByTagName("script")[0],a;h.className+=" wf-loading";tk.src='https://use.typekit.net/'+config.kitId+'.js';tk.async=true;tk.onload=tk.onreadystatechange=function(){a=this.readyState;if(f||a&&a!="complete"&&a!="loaded")return;f=true;clearTimeout(t);try{Typekit.load(config)}catch(e){}};s.parentNode.insertBefore(tk,s)
})(document);
</script>
and here is my render function where i would like to have the javascipt load asyn, it's super easy in a html file, however i am stunned within a react component on how to do acheive. (If i can avoid install another external module would be best). Many thanks
render() {
return (
<head>
//Script to load asyn
</head>
)
}

My server renders the initial HTML document, so I couldn't just insert it into the head as #mjhm suggested. Using #bluebill1049's answer I was able to make use of ES6 Template Strings and react's dangerouslySetInnerHTML
import React, {Component} from 'react';
export default class Html extends Component {
render() {
const loadTypeKit = `
(function(d) {
var config = {
kitId: 'YOUR_KITID',
scriptTimeout: 3000,
async: true
},
h=d.documentElement,
t=setTimeout(function() {
h.className=h.className.replace(/wf-loading/g,"")+" wf-inactive";
},config.scriptTimeout),
tk=d.createElement("script"),
f=false,
s=d.getElementsByTagName("script")[0],
a;
h.className+=" wf-loading";
tk.src='https://use.typekit.net/'+config.kitId+'.js';
tk.async=true;
tk.onload=tk.onreadystatechange=function() {
a=this.readyState;
if(f||a&&a!="complete"&&a!="loaded") return;
f=true;
clearTimeout(t);
try{
Typekit.load(config)
} catch(e){ }
};
s.parentNode.insertBefore(tk,s);
})(document);
`;
return (
<html>
<head>
<script dangerouslySetInnerHTML={{__html: loadTypeKit}} />
</head>
<body>
...
</body>
</html>
);
}
}

dangerouslySetInnerHTML is the solution i have found.
https://facebook.github.io/react/tips/dangerously-set-inner-html.html
This allow you to have script tag and insert JavaScript in side of jSX.

Related

Externally displayed Json Data not passed to reactjs components says error "embedded:44 Uncaught TypeError: Cannot read property 'state' of undefined"

My head is burning. For several hours of works am having the error below when passing externally retrieved json data to reactjs components
here is the error
embedded:44 Uncaught TypeError: Cannot read property 'state' of undefined
below is the code. can someone help me fix the issue. I will appreciate any assistance no matter how little. Thanks
<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<title>React Tutorial</title>
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<div id="app"></div>
<script type="text/babel">
var Test = React.createClass({
render:function(){
return(
<App/>
);
}
});
class App extends React.Component {
constructor(){
super()
this.state = {
data: []
}
}
componentDidMount() {
$.ajax({
url: "data1.json",
type: "GET",
dataType: 'json',
ContentType: 'application/json',
success: function(data) {
var da = this.setState({data: data});
console.log(da);
}.bind(this),
error: function(jqXHR) {
console.log(jqXHR);
}.bind(this)
})
}
render(){
return <p>The data is: {this.state.data}</p>;
}
}
ReactDOM.render( <Test details={this.state.data}/>, document.getElementById( "content" ) );
</script>
<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="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
</body>
</html>
data1.json
[
{
no: "1",
qtext:"sssssssCalifornia is in which part of USA?",
options:[
{text:"East"},
{text:"Mid"},
{text:"West"},
{text:"South"}
],
ans:"West",
marks: 3
},
{
no: "2",
qtext:"Who is Prime Minister of India?",
options:[
{text:"Sonia Gandhi"},
{text:"Narendra Modi"},
{text:"Manmohan Singh"},
{text:"Rahul Gandhi"}
],
ans:"Narendra Modi",
marks: 2
},
{
no: "3",
qtext:"Which of the following is most popular Search Engine Company?",
options:[
{text:"Microsoft"},
{text:"Facebook"},
{text:"Google"},
{text:"Yahoo"}
],
ans:"Google",
marks: 1
},
]
The reason why you get that error is because data is in fact not defined. As you can see, the data variable is defined inside the if-statement of the callback function, which is out of the scope of the React component.
Try declaring the data variable just after the script tag:
<script>
var data;
function loadJSON() {
...
</script>
and remove the var in front of data where it's value gets assigned (in the line var data=JSON.stringify(this.responseText);).
This however, will not solve your issues because the react component will never receive the value you are looking for. My recomendation is to move the data request (the loadJSON()) inside the react component, call it in the componentWillMount method of the react component and save the resulting data in the component's state. Something like:
class Test extends React.Component{
constructor(){
this.state = {};
}
componentWillMount(){
this.loadJSON();
}
loadJSON() {
var http = new XMLHttpRequest();
var self = this;
http.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
self.setState({data: JSON.parse(this.responseText)});
}
};
http.open("GET", "data1.json", true);
http.send();
}
render(){
return <p>The data is: {this.state.data}</p>;
}
}
Use super(props) inside your constructor(props) to access this.state.
‘this’ keyword is uninitialized without calling super(props).
constructor(props){
super(props);
this.state={};
}

AngularJS: Function in controller is called three times

I have a controller which calls a save service, and it is for some reason triggered three times. It is not that great if it saves three instances every time it is triggered. Is my approach of solving it wrong?
I found this following article which says it is a normal behavior in AngularJS
Below is an example which triggers that behavior. I'm using webpack to bundle AngularJS, and other dependencies.
FooCtrl
import {IFooService} from "./FooService";
export class FooCtrl {
static $inject = ["FooService"];
public fooService: IFooService;
constructor(fooService: IFooService) {
console.log("creating foo controller");
this.fooService = fooService;
}
public callService(): boolean {
console.log("call service");
this.fooService.save();
console.log(this.fooService.getSaves());
return true;
}
}
FooService
export interface IFooService {
save(): void;
getSaves(): number;
}
export class FooService implements IFooService{
private saves: number = 0;
public save(): void {
console.log("saved");
this.saves++;
}
public getSaves(): number {
return this.saves;
}
}
Main
namespace Main {
let fooModule = new FooModule.Module();
let main = angular.module("myApp", [
"ngRoute",
fooModule.getFooModule(),
]);
main.controller("BarCtrl", function($scope) {
$scope.message = "Bar";
});
main.config(function($routeProvider: ng.route.IRouteProvider) {
$routeProvider.when("/", {
"templateUrl": "foo/index.html",
});
});
}
index.html
<!doctype html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8">
<title></title>
<script src="http://localhost:8080/webpack-dev-server.js"></script>
<script src="vendors.bundle.js"></script>
<script src="app.bundle.js"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
index.part.html
<div ng-controller="FooCtrl as ctrl">
<p ng-bind="ctrl.callService()"></p>
</div>
Because you are binding your method to the <p> element, it will be trigger on every digest cycle so that angular can check if the value changed.
I am not sure what you are trying to do exactly, but it looks like this this method should be trigger by a user action or maybe periodically and controlled using $timeout service.
Read more about scope digest cycle in the official documentation.

Can not bind to service's returned promise with Angular (ES2015)

I'm learning to use Angular with ES2015 (using webpack) and while playing with the features I have encountered a problem. I can't seem to find what I'm missing so maybe one of you could direct me to the right direction.
Currently I'm simply trying to bind a controller to a service that returns mock data.
Cat Service
catData.js just returns a mock array of cat objects
import {cats} from './catsData.js';
export default class catService {
getData() {
return Promise.resolve(cats);
}
}
Cat Controller
export default class CatController {
constructor(catService) {
this._catService = catService;
this.cats = [];
this.activate();
}
activate() {
this.getData();
}
getData() {
this._catService.getData().then(cats=> {this.cats= cats;});
}
}
app.js
Where I bootstrap it all
import angular from 'angular';
const app = angular.module('catApp', []);
import catController from './catController.js';
import catService from './catService.js';
app
.controller('catController', catController)
.service('catService', catService);
Simple test view
<body ng-app="catApp">
<div class="container" ng-controller="catController as vm>
<ul>
<li ng-repeat="cat in vm.cats">
<div class="well">
{{cat.name}}
{{cat.age}}
</div>
</li>
</ul>
</div>
<script src="bundle.js"></script>
</body>
I want to clarify that before I used Promise.resolve on my catService, I returned the data synchronically and everything worked. Now, the list of cats does not appear (No console error either)
So after further digging I found that ES6 Promises do not play well with angular and used $q instead. This has fixed the issue:
import {cats} from './catsData.js';
export default class catService {
constructor($q) {
this.$q = $q;
}
getData() {
let defer = this.$q.defer();
defer.resolve(cats);
return defer.promise;
}
}
catServoce.$inject = ['$q'];

Can I mount a React component automatically (like angularjs' direct)?

Update 3
I have been using AngularJs for several years, and want to try ReactJs. With AngularJs I can define a directive and put the node inside the HTML DOM, like this:
<!DOCTYPE html>
<html>
<head>
....
</head>
<body>
<!--helloWorld is a directive-->
<hello-world></hello-world>
</body>
</html>
However, in React, to my knowledge, one needs to call
React.render(
<HelloWorld />,
targetElement);
to mount the component. Is there a way in React to mount the component automatically?
I have created a codepen here to show my idea as an experiment. The main piece of code is as below:
function R() {
var className = 'Hello';
if (window[className]) {
var elements = document.getElementsByTagName(className);
angular.forEach(elements, function (element) {
React.render(React.createElement(window[className]), element);
});
}
setTimeout(R, 50);
}
Every 50ms, we will check if the Class has been created. If it is I will render it under the element in the real DOM node <Hello> </Hello>.
*Warning: this code works only in trivial cases.
The JSX code to create react class:
<script type="text/jsx">
var Hello = React.createClass({
render: function() {
return <h1>Hello</h1>;
}
});
// React.render could be called here, like below:
React.render(
<Hello> </Hello>,
document.getElementById('example')
);
// but I want the <Hello> </Hello> stays in the html DOM. So that I don't
// repeat calling React.render. The first piece of code, will find the
// node and render the ReactClass there.
</script>
In html:
<Hello> </Hello>
I don't want to use setTimeout, but don't know if there are other approaches.
I don't really understand what you're trying to do but you could use EventEmitter
to bind a listener when you class is created.
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var myclassfactory= assign({}, EventEmitter.prototype, {
addListener: function(myclassName,callback) {
this.on(myclass,callback);
},
createClass: function(myclassName) {
//create your class ===>>>>>> React.CreateClass !!
this.emit(myclassName);
}
});
Then add a listener in your view:
myclassfactory.addListener(myclassName, render(myclassName));
var render = function(myclassName) {
React.render(myclassName, myelement);
}

Set document title on client and server side

I want to know how you set the title of each page with ReactJS. And more, I use react-router-component and i want use same tech to set title to each page at server side by using React.renderComponentToString.
My current root Component :
module.exports = App = React.createClass
getInitialState: ->
return title: 'My title'
render: ->
<html lang="fr">
<head>
<link rel="stylesheet" href="/assets/css/main.css" />
<script src="/assets/js/bundle.js"></script>
<title>{#state.title}</title>
</head>
<body>
<div id="body-content">
<div id="main-container">
<Content path={#props.path} />
</div>
</div>
</body>
</html>
And my Content component :
Content = React.createClass
render: ->
<Locations id="main" className="App" path={#props.path}>
<Location path="/" handler={MainPage} />
<Location path="/users/:username" handler={UserPage} />
</Locations>
Top-level React component
var React = require('react');
var AppStore = require('../stores/AppStore');
var Application = React.createClass({
propTypes: {
path: React.PropTypes.string.isRequired,
onSetTitle: React.PropTypes.func.isRequired
},
render() {
var page = AppStore.getPage(this.props.path);
this.props.onSetTitle(page.title);
return (
<div className="container">
<h1>{page.title}</h1>
<div return <div dangerouslySetInnerHTML={{__html: page.body}}>;
</div>
);
}
});
module.exports = Application;
Client-side startup script (entry point)
var React = require('react');
var Dispatcher = require('./core/Dispatcher');
var Application = require('./components/Application');
Dispatcher.register((payload) => {
var action = payload.action;
if (action.actionType === ActionTypes.CHANGE_LOCATION) {
app.setProps({path: action.path});
}
});
var app = React.createElement(Application, {
path: window.location.pathname,
onSetTitle: (title) => document.title = title
}));
app = React.render(app, document.body);
More info: https://gist.github.com/koistya/24715d295fbf710d1e24
Demo Project: https://github.com/kriasoft/react-starter-kit
to pass the title to your App component server side, you have to pass it the same way as you're passing the path, i.e. as props and not state.
So first you'll need to change:
<title>{#state.title}</title>
To:
<title>{#props.title}</title>
Then in your backend pass the wanted title to the App component when instantiating it, like so:
var url = require('url');
var ReactAsync = require('react-async');
var App = require('./path/to/your/root-component.jsx');
// app = your express app:
app.get('*', function (req, res) {
var path = url.parse(req.url).pathname;
var title = getTitleFromPath(path); // Made up function to get title from a path..
ReactAsync.renderComponentToStringWithAsyncState(
App({path: path, title: title}),
function (err, markup) {
if (err) return res.send(500, err);
res.send('<!DOCTYPE html>\n' + markup);
}
);
});
Hope that helps!
As for setting the title client side I think your solution with setting the document.title probably is the best option.
Update
I've now tested the above and the title is set correctly, however React gets a different checksum for the server generated component and the client generated one, which is no good:
Uncaught Error: Invariant Violation: You're trying to render a component to the document using server rendering but the checksum was invalid
So actually you shouldn't use this solution!
Will have to play around a little more...

Resources