Global React does not play nice with AMD React - reactjs

I'm getting weird weird behaviour when rendering a component using an AMD-loaded React, when a global React already exists on the page. Click events on components are getting fired when they should not be.
A look at the DOM implies that this stems from multiple React instances (one global, one AMD in my case) not being aware of each other, but this poses a problem when loading an AMD module at runtime that depends on React, into a page that also includes React.
How can I resolve this clash?
Reproduction
I can make a component like this:
var ButtonComponent = React.createClass({
onButtonClick: function(){
alert(this.props.data + ' click event fired');
},
render: function() {
return React.DOM.button({onClick: this.onButtonClick}, this.props.data);
}
});
(function(){ // create vanilla
var ButtonList = React.createClass({
render: function() {
return React.DOM.div({}, React.createElement(ButtonComponent, {data: this.props.data}));
}
});
React.render(React.createElement(ButtonList, {data: 'button that was loaded by the page'}), document.getElementById('page-load-target'));
})();
jsbin
But as soon as I add another component using another instance of React then click the first button, then it calls the click event on the second loaded button:
// .... as above ....
(function(){ // create using amd
require.config({
paths: {
'react': '//fb.me/react-with-addons-0.12.2.min'
}
});
window.setTimeout(function(){
require(['react'], function(ReactFromAmd){
ReactFromAmd.render(ReactFromAmd.createElement(ButtonComponent, {data: 'button that was loaded by AMD'}), document.getElementById('amd-load-target'));
});
}, 1000)
})();
jsbin
If I use the existing, global version of React in this call (rather than ReactFromAmd, then it works as expected. jsbin

The ancestors (React instance) of ButtonComponent and the component created with ReactFromAmd.createElement are different, and yet they are in the same virtual DOM -- That's not allowed.
If you don't mind replacing AMD with browserify, I just figured out a way to let isolated/remotely-loaded React components co-exist nicely.
(To be continued if someone needs it)

This has been fixed in version 0.14.2: http://jsbin.com/tesodoxape/1/edit?html,js,output

Related

Backbone View overriding the render function loses childviews

I'm trying to run some code to resize a div after my header is done rendering. I have looked at answers here and the Backbone documentation. this is what I wrote:
Backbone.View.extend({
template: header_tpl,
render: function() {
this.$el.html(this.template({});
setTimeout(function() {
$(window).on("resize",function(){
$(".somediv").height($(".someotherdiv").height())
})
.resize()
}, 0);
return this;
},
childViews: {
// Some childViews in here
}
});
This works, but the childViews in this view won't render. I think it has to do with the empty object being passed on the this.template(). The backbone docs say to pass on this.model.attributes, but this view doesn't have a model. Its a simple header with no data being passed on to it.
As pointed out by #CoryDanielson 's comment, Backbone has no default handling of "childViews". If your job is to make a Backbone View render it's child Views, there are lots of reasonably simple ways to do that.
But I think what you are really trying to do is to keep some sort of pre-built render functionality that is built into Backbone.View somewhere else in your codebase. Since the only extension you seem to need is attaching a resize event to window, maybe the best option is to not do this in the render method, then you can continue to use whatever is pre-built elsewhere in your codebase.
Backbone.View.extend({
template: header_tpl,
// no override of render
initialize: function() {
setTimeout(function() {
$(window).on("resize",function(){
$(".somediv").height($(".someotherdiv").height())
})
.resize()
}, 0);
},
childViews: {
// Some childViews in here
}
});
This code should attach the event when the view is instanced, not at each render.
Of course, if your Codebase may also be altering the default initialize method, we really can't know. In that case, there might be some options to override the default methods (initialize, render, ...) just by extending, but still calling the old methods under the hood.

Setting the initial state in React components for progressive enhancement & Flux architecture

I've read on http://scotch.io/tutorials/javascript/build-a-real-time-twitter-stream-with-node-and-react-js and it describes a technique of taking over server rendered React components seamlessly:
Server renders into {{{markup}}} in handlebars, and pass initial state.
<section id="react-app">{{{ markup }}}</div>
<script id="initial-state" type="application/json">{{{state}}}</script>
Then on the client side javascript
/** #jsx React.DOM */
var React = require('react');
var TweetsApp = require('./components/TweetsApp.react');
// Snag the initial state that was passed from the server side
var initialState = JSON.parse(document.getElementById('initial-state').innerHTML)
// Render the components, picking up where react left off on the server
React.renderComponent(
<TweetsApp tweets={initialState}/>,
document.getElementById('react-app')
);
But in a flux architecture, such as described in this article http://scotch.io/tutorials/javascript/creating-a-simple-shopping-cart-with-react-js-and-flux, state is initialized in the getInitialState lifecycle method:
// Method to retrieve state from Stores
function getCartState() {
return {
product: ProductStore.getProduct(),
selectedProduct: ProductStore.getSelected(),
cartItems: CartStore.getCartItems(),
cartCount: CartStore.getCartCount(),
cartTotal: CartStore.getCartTotal(),
cartVisible: CartStore.getCartVisible()
};
}
// Define main Controller View
var FluxCartApp = React.createClass({
// Get initial state from stores
getInitialState: function() {
return getCartState();
},
// Add change listeners to stores
componentDidMount: function() {
ProductStore.addChangeListener(this._onChange);
CartStore.addChangeListener(this._onChange);
},
// Remove change listers from stores
componentWillUnmount: function() {
ProductStore.removeChangeListener(this._onChange);
CartStore.removeChangeListener(this._onChange);
},
// Render our child components, passing state via props
render: function() {
return (
<div className="flux-cart-app">
<FluxCart products={this.state.cartItems} count={this.state.cartCount} total={this.state.cartTotal} visible={this.state.cartVisible} />
<FluxProduct product={this.state.product} cartitems={this.state.cartItems} selected={this.state.selectedProduct} />
</div>
);
},
// Method to setState based upon Store changes
_onChange: function() {
this.setState(getCartState());
}
});
module.exports = FluxCartApp;
Which one is the right approach to setting state from a progressive enhancement point of view?
Thinking about progressive enhancement I like how flux and react work together.
I am using ReactJS and Flux in my current project and everything is clean and easy. All you have to be aware of is showing some discipline of creating new stores when it really is needed. I dont really like the eventEmitter stuff though. I just trigger my own events which I define in a seperate eventConstants.js file this allows me to have several components listening for different changes on the same store.
This really scales well.
Answering your question:
It does depend about your usecase. Ignoring that rendering an initial page on the server is great for SEO it does only make sence to render on the server if users should all see pretty much the same content. I like to keep client stuff on the client.
I hope this helped you

Using x-editable with reactjs, how to call editable function on items?

I am new to ReactJS, I am trying to create a Reactjs component that is a list of editable fields using x-editable.
According to x-editable documentation I need to call .editable() for each field, normally this is done accessing the element with jQuery.
How can I do it with React ? It has to be done when the component is mounted and I can't find a way to know when the component is rendered...
I would like to know the best way to achieve this ; I had a look at React refs but I am not sure it can help. So I ended up giving it a specific class, then using a selector
to call editable() on the fields, but it works only when the rendering has been done
and I don't find it very elegant.
JS(X) code:
var EditableField = React.createClass({
render: function() {
return <p>{this.props.name}: {this.props.value}</p>
}
});
in script code:
$(document).ready(function() {
$.fn.editable.defaults.mode = 'inline';
$(".editable_field").editable();
You should do it in the componentDidMount function. This function is called when the component is mounted and the DOM is ready. Read more about the lifecycle here: http://facebook.github.io/react/docs/component-specs.html
Example (using refs instead of class selector):
var EditableField = React.createClass({
componentDidMount: function() {
$(this.refs.editable.getDOMNode()).editable()
},
render: function() {
return <p>{this.props.name}: {this.props.value}</p>
}
});
Note that many "jquery plugins" will do heavy DOM manipulations and that does not always play well with React.
You can use react-x-editable(react version of x-editable).Currently not all option supported.I will try to support most of options.

Backbone Router & Deep Linking

My single page web application consists of 4-5 views stacked vertically, when a user chooses a menu item, the page will scroll to the appropriate view. When you come into the application for the first time this is not a problem, however if you deep link to a menu item my page throws a fit because it's trying to access properties of an element that does not yet exists.
The problem I am having is understanding why the elements do not exist at the time the router is trying to scroll the page.
If you load / and then select home no problems, but if you directly hit #home via browser that when I get jQuery undefined errors.
Uncaught TypeError: Cannot read property 'top' of undefined
Inside router I am instantiating and rendering all of my views within the initialize function. The idea is the initialize will always happen before any of my routes, clearly not the case.
Again I've read a few threads that show how to have a before and after function for either all routes of individual routes but even using that approach scrollToById fails because it doesn't know what $(id) is at the time of being called.
define(function (require, exports, module) {
var Backbone = require('backbone');
return Backbone.Router.extend({
initialize: function(){
require(['ui/menu/menu','ui/home/home', 'ui/samples/samples', 'ui/resume/resume', 'ui/contact/contact'],
function(Menu, Home, Samples, Resume, Contact){
var menu = new Menu();
menu.render();
var home = new Home();
home.render();
var samples = new Samples();
samples.render();
var resume = new Resume();
resume.render();
var contact = new Contact();
contact.render();
});
},
routes: {
'' : 'init',
'home' : 'home',
'samples' : 'samples',
'resume' : 'resume',
'contact' : 'contact'
},
init: function(){
},
home: function (){
this.scrollToById($(".home-container"));
},
samples: function(){
this.scrollToById($(".samples-container"));
},
resume: function(){
this.scrollToById($(".resume-container"));
},
contact: function(){
this.scrollToById($(".contact-container"));
},
scrollToById: function(id) {
var val = $(id).offset().top - 127;
$('html, body').animate({
scrollTop: val
}, 2000);
}
});
});
Appreciate any tips or advice.
I think the routes event handlers in the router are getting initialized at the same time as the initialize function. Because of this, route events are getting triggered before the DOM elements are rendered.
I would try making a new function outside of Router that contains everything currently inside the initialize function. Then the final thing in that function can be to create an instance of the router. This will ensure that no routes events are called until your scripts and DOM are loaded.

Understanding Backbone architecture base concepts

I'm trying to working with backbone but I'm missing it's base concepts because this is the first JavaScript MVVM Framework I try.
I've taken a look to some guide but I think I still missing how it should be used.
I'll show my app to get some direction:
// Search.js
var Search = {
Models: {},
Collections: {},
Views: {},
Templates:{}
};
Search.Models.Product = Backbone.Model.extend({
defaults: search.product.defaults || {},
toUrl:function (url) {
// an example method
return url.replace(" ", "-").toLowerCase();
},
initialize:function () {
console.log("initialize Search.Models.Product");
}
});
Search.Views.Product = Backbone.View.extend({
initialize:function () {
console.log("initialize Search.Views.Product");
},
render:function (response) {
console.log("render Search.Views.Product");
console.log(this.model.toJSON());
// do default behavior here
}
});
Search.Models.Manufacturer = Backbone.Model.etc...
Search.Views.Manufacturer = Backbone.View.etc...
then in my web application view:
<head>
<script src="js/jquery.min.js"></script>
<script src="js/underscore.min.js"></script>
<script src="js/backbone/backbone.min.js"></script>
<script src="js/backbone/Search.js"></script>
</head>
<body>
<script>
var search = {};
search.product = {};
search.product.defaults = {
id:0,
container:"#search-results",
type:"product",
text:"<?php echo __('No result');?>",
image:"<?php echo $this->webroot;?>files/product/default.png"
};
$(function(){
var ProductModel = new Search.Models.Product();
var ProductView = new Search.Views.Product({
model:ProductModel,
template:$("#results-product-template"),
render:function (response) {
// do specific view behavior here if needed
console.log('render ProductView override Search.Views.Product');
}
});
function onServerResponse (ajax_data) {
// let's assume there is some callback set for onServerResponse method
ProductView.render(ajax_data);
}
});
</script>
</body>
I think I missing how Backbone new instances are intended to be used for, I thought with Backbone Search.js I should build the base app like Search.Views.Product and extend it in the view due to the situation with ProductView.
So in my example, with render method, use it with a default behavior in the Search.js and with specific behavior in my html view.
After some try, it seems ProductModel and ProductView are just instances and you have to do all the code in the Search.js without creating specific behaviors.
I understand doing it in this way make everything easiest to be kept up to date, but what if I use this app in different views and relative places?
I'm sure I'm missing the way it should be used.
In this guides there is no code used inside the html view, so should I write all the code in the app without insert specific situations?
If not, how I should write the code for specific situations of the html view?
Is it permitted to override methods of my Backbone application?
Basically, you should think of the different parts like this:
templates indicate what should be displayed and where. They are writtent in HTML
views dictate how the display should react to changes in the environment (user clicks, data changing). They are written in javascript
models and collections hold the data and make it easier to work with. For example, if a model is displayed in a view, you can tell the view to refresh when the model's data changes
then, you have javascript code that will create new instances of views with the proper model/collection and display them in the browser
I'm writing a book on Marionette.js, which is a framework to make working with Backbone easier. The first chapters are available in a free sample, and explain the above points in more detail: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf

Resources