how to use one component render many html fragment in reactjs? - javascript

I have a button there, when I click this button, i want render a div and append it to body.
and when I click this button again, a new div be rendered.
I want: How many times I click the button, how many div be render.
The follow code can only render one div: ( jsFiddle: http://jsfiddle.net/pw4yq/ )
var $tool = document.getElementById('tool');
var $main = document.getElementById('main');
var partBox = React.createClass({displayName: 'partBox',
render: function(){
return (
React.DOM.div({className:"box"}, "HELLO! ", this.props.ts)
)
}
});
var createBoxBtn = React.createClass({displayName: 'createBoxBtn',
createBox: function(){
var timeStamp = new Date().getTime();
React.renderComponent(partBox( {ts:timeStamp} ), $main);
},
render: function(){
return (
React.DOM.button( {onClick:this.createBox}, "createBox")
)
}
});
React.renderComponent(createBoxBtn(null ), $tool);

Your app should be data driven, meaning the state of your app is kept outside the DOM. In your example, you are essentially keeping a list of Date objects. Put that into a state that you can modify, and render a box for each Date object you have created:
Working JSFiddle: http://jsfiddle.net/pw4yq/6/
var $main = document.getElementById('main');
var partBox = React.createClass({displayName: 'partBox',
render: function(){
return (
React.DOM.div({className:"box"}, "HELLO! ", this.props.ts)
)
}
});
var createBoxBtn = React.createClass({displayName: 'createBoxBtn',
createBox: function(){
var timeStamp = new Date().getTime();
this.props.onClick({ts: timeStamp});
},
render: function(){
return (
React.DOM.button({onClick: this.createBox}, "createBox")
)
}
});
var app = React.createClass({
displayName: "app",
getInitialState: function() {
return {
partBoxes: []
};
},
createBox: function(partBox) {
this.state.partBoxes.push(partBox);
this.forceUpdate();
},
render: function() {
return (
React.DOM.div(null,
createBoxBtn({onClick: this.createBox}),
this.state.partBoxes.map(function(pb) {
return partBox({key: pb.ts, ts: pb.ts});
})
)
);
}
});
React.renderComponent(app(null), $main);

Related

React onClick not working to start a timer

I know there have been a few of these questions posted but I haven't been able to find an answer to work. My code is below. Everything links, and I was able to get a more simple timer working that just starts timing when the component mounts, but now I want to make it more interactive. however, when I click the button nothing happens. What I am trying to do is have a component that is a button, when clicked it becomes a timer and displays the amount of time passed since clicked. But clicking the button doesn't do any thing.
thanks for the help!
var StartButton = React.createClass({
getInitialState:function(){
return { elapsed: 0, go: false };
},
getDefaultProps: function() {
return {
interval: 1000
};
},
componentDidMount: function(){
console.log('mounted');
},
tick: function(){
console.log('tick')
this.setState({elapsed: new Date() - this.state.start});
},
startCount: function(e){
console.log(e);
console.log('GO!!!')
this.state.props = Date.now;
this.state.go = true;
this.timer = setInterval(this.tick, this.props.interval);
},
render: function(){
var self = this;
var elapsed = Math.round(this.state.elapsed / 100);
var seconds = (elapsed / 10).toFixed(3);
var clickMe = <button onCLick={self.startCount} >GO</button>;
var display = <div>time elapsed {this.state.elapsed}</div>
return (
<div>
{this.state.go ? display : clickMe}
</div>
)
}
})
var AppContainer = React.createClass({
render: function(){
return (<div><StartButton interval={1000} /></div>);
}
})
$(document).ready(function () {
console.log('in ready');
ReactDOM.render(<AppContainer></AppContainer>, document.getElementById('jake'));
});
Not sure if you found all the small errors by now, but in case you haven't, here's a working copy of your component;
var StartButton = React.createClass({
getInitialState:function(){
return { elapsed: 0, go: false };
},
getDefaultProps: function() {
return {
interval: 1000
};
},
componentDidMount: function(){
console.log('mounted');
},
tick: function(){
console.log('tick')
this.setState({elapsed: Date.now() - this.state.start});
},
startCount: function(e){
console.log(e);
console.log('GO!!!')
this.setState({start: Date.now(), go: true})
setInterval(this.tick, this.props.interval);
},
render: function(){
var clickMe = <button onClick={this.startCount} >GO</button>;
var display = <div>time elapsed {Math.round(this.state.elapsed / 1000)} seconds</div>
return (
<div>
{this.state.go ? display : clickMe}
</div>
)
}
})
I think you problem is here with onClick:
var clickMe = <button onClick={self.startCount} >GO</button>;

How do I trigger an event on a higher-up element in React?

I'm only a few hours new to React, so I might have missed something obvious. I have an app which looks a bit like this:
var App = React.createClass({
myAction: function(thingId) {
this.setState({currentThing: thingId});
},
render: function() {
return (<ThingsContainer app={this}/>);
}
});
var ThingsContainer = React.createClass({
render: function() {
return (<ThingList app={this.props.app}/>);
}
});
var ThingList = React.createClass({
render: function() {
var self = this;
var thingNodes = this.props.data.map(function (thing) {
return (<Thing thing={thing} app={self.props.app} key={thing.id}></Thing>);
});
return (<div>{thingNodes}</div>);
}
});
var Thing = React.createClass({
performAction: function() {
this.props.app.myAction(this.props.thing.id);
},
render: function() {
return (
<div>
<h2>{this.props.thing.title}</h2>
<button onClick={this.performAction}>pip!</button>
</div>
);
}
});
React.render(<App />, document.getElementById('content'));
I want to trigger an event on the top-level object from a lower-level object. The relevant page doesn't seem to address this situation directly.
In my solution I'm passing down the app object several levels. This doesn't feel right. In Ember I would be able to use a singleton Controller. In Angular I'd probably use a service. In Backbone or jQuery I'd use an event.
I don't know how much magic wiring of this sort to expect from React.
Is my above solution, which involves explicit wiring between components, even across several edges, the right approach?
I would just pass down the function instead of the whole object:
var App = React.createClass({
myAction: function(thingId) {
this.setState({currentThing: thingId});
},
render: function() {
return (<ThingsContainer myAction={this.myAction}/>);
}
});
var ThingsContainer = React.createClass({
render: function() {
return (<ThingList myAction={this.props.myAction}/>);
}
});
var ThingList = React.createClass({
render: function() {
var self = this;
var thingNodes = this.props.data.map(function (thing) {
return (<Thing thing={thing} myAction={this.props.myAction} key={thing.id}></Thing>);
});
return (<div>{thingNodes}</div>);
}
});
var Thing = React.createClass({
performAction: function() {
this.props.myAction(this.props.thing.id);
},
render: function() {
return (
<div>
<h2>{this.props.thing.title}</h2>
<button onClick={this.performAction}>pip!</button>
</div>
);
}
});
React.render(<App />, document.getElementById('content'));
other than that I don't see anything wrong with your approach, it does feel a bit strange at first but the nice thing about it is that the parent element is always responsible for directly modifying state, and it's very easy to debug issues like this since there is a very clear and concise 'flow'.

Custom image toggle button in ReactJS

I have this ReactJS code to show a custom image button that toggles between 2 different images for ON and OFF state. Is there a simpler way to do this? I was hoping CSS might be less lines of code, but wasn't able to find a simple example.
The code below passes state up from <MyIconButton> to <MyPartyCatButton> then to <MyHomeView>. My app will have 4 of these custom buttons on the home screen, which is why I factored out <MyIconButton>.
btw - this is for a mobile App and I read (and noticed this myself) it's really slow using checkboxes on mobile browsers; that's why I chose to try this without using checkboxes.
ReactJS code
var MyIconButton = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
console.log("INSIDE: MyIconButton handleSubmit");
// Change button's state ON/OFF,
// then sends state up the food chain via
// this.props.updateFilter( b_buttonOn ).
var b_buttonOn = false;
if (this.props.pressed === true) {
b_buttonOn = false;
}
else {
b_buttonOn = true;
}
// updateFilter is a 'pointer' to a method in the calling React component.
this.props.updateFilter( b_buttonOn );
},
render: function() {
// Show On or Off image.
// ** I could use ? : inside the JSX/HTML but prefer long form to make it explicitly obvious.
var buttonImg = "";
if (this.props.pressed === true) {
buttonImg = this.props.onpic;
}
else {
buttonImg = this.props.offpic;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<input type="image" src={buttonImg}></input>
</form>
</div>
);
}
});
// <MyPartyCatButton> Doesn't have it's own state,
// passes state of <MyIconButton>
// straight through to <MyHomeView>.
var MyPartyCatButton = React.createClass({
render: function() {
return (
<MyIconButton pressed={this.props.pressed} updateFilter={this.props.updateFilter} onpic="static/images/icon1.jpeg" offpic="static/images/off-icon.jpg"/>
);
}
});
//
// Main App view
var MyHomeView = React.createClass({
getInitialState: function() {
// This is where I'll eventually get data from the server.
return {
b_MyPartyCat: true
};
},
updatePartyCategory: function(value) {
// Eventually will write value to the server.
this.setState( {b_MyPartyCat: value} );
console.log("INSIDE: MyHomeView() updatePartyCategory() " + this.state.b_MyPartyCat );
},
render: function() {
return (
<div>
<MyPartyCatButton pressed={this.state.b_MyPartyCat} updateFilter={this.updatePartyCategory}/>
</div>
// Eventually will have 3 other categories i.e. Books, Skateboards, Trees !
);
}
});
if you update the coponent 'pressed' prop dynamically (like you did), simply
var MyIconButton= React.createClass({
render: function(){
var pic= this.props.pressed? this.props.onpic : this.props.offpic
return <img
src={pic}
onClick={this.props.tuggleSelection} //updateFilter is wierd name
/>
}
})
(EDIT: this way, on MyPartyCatButton component, you can pass function to handle 'tuggleSelection' event. event function argument is an event object, but you have the button state allready in the wrapper state (the old one, so you should invert it). your code will be something like that:
render: function(){
return <MyIconButton pressed={this.state.PartyCatPressed} tuggleSelection={this.updatePartyCategory} />
}
updatePartyCategory: function(e){
this.setState(
{PartyCatPressed: !this.state.PartyCatPressed} //this invert PartyCatPressed value
);
console.log("INSIDE: MyHomeView() updatePartyCategory() " + this.state.b_MyPartyCat )
}
)
but if you don't, use prop for defult value:
var MyIconButton= React.createClass({
getInitialState: function(){
return {pressed: this.props.defultPressed}
},
handleClick: function(){
this.setState({pressed: !this.state.pressed})
},
render: function(){
var pic= this.state.pressed? this.props.onpic : this.props.offpic
return <img
src={pic}
onClick={this.handleClick}
/>
}
})

Change Views Content based on different modules event

My content box module enumerates a collection and creates a container view for each item ( passing the model to the view). It sets the initial content to the content property of its model. Base on a layout property in the model the container view is attached to the DOM. This is kicked off by the “_contentBoxCreate” method.
The content box module responds to clicks to sub items in a sidemenu. The sidemenu is implemented in a different module. The sidemenu sub click event passes an object along as well that contains a sub_id and some text content. I want to take the content from this object and use it to update container view(s).
Currently I’m doing this via the “_sideMenuClick” method. In backbonejs is there a best practice for updating a views content, given that no data was changed on its model?
thanks,
W.L.
APP.module("contentbox", function(contentbox) {
//Model
var Contentbox = Backbone.Model.extend({});
//Collection
var Contentboxes = Backbone.Collection.extend({
model: Contentbox,
url: 'ajax/contentboxResponse/tojson'
});
/*
* View:
*/
var Container = Backbone.View.extend({
initialize: function() {
contentbox.on('update', jQuery.proxy(this.update, this));
contentbox.on('refresh', jQuery.proxy(this.render, this));
var TemplateCache = Backbone.Marionette.TemplateCache;
this.template = TemplateCache.get("#contentbox-container");
},
render: function() {
var content = this.model.get('content').toString();
var html = this.template({content: content});
this.$el.html(html);//backbone element
return this;
},
update: function(fn) {
var content = fn.apply(this);
if (content !== null) {
var html = this.template({content: content});
this.$el.html(html);
}
}
});
//collection
var contentboxes = new Contentboxes();
var _sideMenuToggle = function() {
contentbox.trigger('refresh');
};
var _sideMenuClick = function(sideMenu) { //view contex
var fn = function() {
// this fn will have the context of the view!!
var linksub = this.model.get('linksub').toString();
if (linksub === sideMenu.id.toString()) {
return sideMenu.content.toString();
}
return null;
};
contentbox.trigger('update', fn);
};
var _contentBoxCreate = function() {
var create = function(cboxes) {
cboxes.each(function(model) {
var layout = "#body-" + model.get('layout');
var $el = jQuery(layout);
var container = new Container({model: model});
$el.append(container.render().$el);
});
};
contentboxes.fetch({
success: create
});
};
this.on("start", function() {
_contentBoxCreate();
});
this.addInitializer(function() {
APP.vent.on('sidemenu:toggle', _sideMenuToggle);
APP.reqres.setHandler('sidemenu:submenu', _sideMenuClick);//event and content...
//from another module
});
});
UPDATE:
Changed the view...
/*
* View
*/
var Container = Backbone.View.extend({
initialize: function() {
this.renderableModel = this.model; // Define renderableModel & set its initial value
contentbox.on('update', this.update, this);
contentbox.on('refresh', this.reset, this); // 3rd param gives context of view
var TemplateCache = Backbone.Marionette.TemplateCache;
this.template = TemplateCache.get("#contentbox-container");
},
render: function() {
var content = this.renderableModel.get('content').toString();
var html = this.template({content: content});
this.$el.html(html);//backbone element
return this;
},
update: function(fn) {
/**
* The "update" event is broadcasted to all Container views on the page.
* We need a way to determine if this is the container we want to update.
* Our criteria is in the fn
*/
var content = fn.apply(this); //criteria match return content, else null.
/*
* The render method knows how to render a contentbox model
*/
if (content !== null) {
this.renderableModel = new Contentbox();
this.renderableModel.set({content: content}); //add content to new contentbox model
this.render(); //Rerender the view
}
},
reset: function() {
this.renderableModel = this.model;
this.render(); // restore view to reflect original model
}
});

Backbone model.change() not firing

I'm currently writing an application using Backbone and for some reason, it doesn't update a view, but only in certain circumstances.
If I refresh the page at index.html#/blog/2 it loads the page just fine, everything works great. However, if I refresh the page at index.html#/blog/1 and then change the URL to index.html#/blog/2 and press enter(NOT refresh), the change never gets fired.
This is my router:
makeChange: function() {
// Set activePage to the current page_id => /blog/2
var attributes = {activePage: this.page_id};
var $this = this;
// Loop through all sections in the app
app.sections.some( function( section ) {
// Check if section has the page
if( !section.validate( attributes ) )
{
// If it has, set the activePage to /blog/2
section.set( attributes, {silent: true} );
// Set active section to whatever section-id was matched
app.set( {activeSect: section.id}, {silent: true} );
console.log('Calling change!');
// Calling change on both the app and the section
app.change();
section.change();
console.log('Change complete!');
return true;
}
});
}
This is the app view(which is referenced as "app" up above^):
var AppView = Backbone.View.extend({
initialize: function( option ) {
app.bind( 'change', _.bind( this.changeSect, this ) );
},
changeSect: function() {
var newSect = app.sections.get( app.get('activeSect' ) );
var newSectView = newSect.view;
if( !app.hasChanged( 'activeSect' ) )
newSectView.activate( null, newSect );
else
{
var oldSect = app.sections.get( app.previous( 'activeSect' ) );
var oldSectView = oldSect.view;
newSectView.activate( oldSect, newSect );
oldSectView.deactivate( oldSect, newSect );
}
}
});
Tell me if you need to see some other classes/models/views.
I solved it! This only happens when navigating between different pages(by changing activePage in the section) in the same section, so activeSect in app was never changed, thus never called changeSect(). Now even when activeSect is the same in the app, and activePage has changed in the section, it will call the changeSect() in the app anyway.
In Section-model, I added this:
initialize: function() {
this.pages = new Pages();
this.bind( 'change', _.bind( this.bindChange, this ) );
},
prepareForceChange: function() {
this.forceChange = true;
},
bindChange: function() {
console.log('BINDCHANGE!');
if( this.forceChange )
{
AppView.prototype.forceChange();
this.forceChange = false;
}
},
In router.makeChange() above this:
section.set( attributes, {silent: true} );
app.set( {activeSect: section.id}, {silent: true} );
I added:
var oldSectId = app.get('activeSect');
if( oldSectId == section.id ) section.prepareForceChange();

Categories

Resources