Custom image toggle button in ReactJS - javascript

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}
/>
}
})

Related

Trigger onClick event for a ReactJS element

I have a list of elements that are loaded with reactjs and at the end of that list there is a button that loads more items via onclick event using reactjs.
I want to create a function that using javascript or jquery, trigger the onclick event to load all the items instead of clicking one by one on the load more items.
I tried to do it using a interval in jquery but the $element.trigger('click') is not working, does nothing.
Can anyone help me with this? please.
ReactJS:
var ConversationShowMore = React.createClass({
getInitialState: function(){
return {show: false, next_comments: ""};
},
loadMoreComments: function(){
this.setState({show: true});
},
render: function(){
var obj = this.props.next_comments || "";
if (obj != "" && requesturl != obj) {
if (this.state.show) {
return (
<ConversationBox url={this.props.next_comments} />
)
}else{
return (
<a onClick={this.loadMoreComments} className="showmoreconversations" href="#" role="button"><span>Load more conversations...</span></a>
)
}
}else{
return (
<div></div>
)
}
}
});
Javascript/jQuery:
var tid = setInterval(myCode, 5000);
function myCode() {
if($("#conversationContainer a.showmoreconversations").length){
$("#conversationContainer a.showmoreconversations").trigger('click');
}else{
abortTimer();
}
}
function abortTimer() {
clearInterval(tid);
}
When component is mounted, you will trigger request to load more comments. When this request is complete, you schedule another request in X miliseconds.
loadMoreComments(){
console.log('loaded more comments');
// Probably, you will trigger async request. Call following line when this request is complete.
this.timeout = window.setTimeout(this.loadMoreComments, 5000);
},
componentDidMount() {
this.loadMoreComments();
},
Remember to cancel scheduled request when unmounting component. Otherwise, it will run virtually forever (and will surely case exception to be thrown)
componentWillUnmount() {
window.clearTimeout(this.timeout);
},
Working example over here: https://jsfiddle.net/69z2wepo/34030/

React render component multiple times in container with different props?

I am trying to create a reusable "tag" React component, so that users can create tags onclick and see that information displayed in the DOM.
Here's the module:
module.exports = React.createClass({
render: function() {
return (
<div className="language chip" data-lang={this.props.language} data-lang-level={this.props.level}>
{this.props.language} ({this.props.level})
<i className="material-icons">close</i>
</div>
);
}
});
And the onclick call:
var addLanguage = $('a#add-language');
addLanguage.click(function() {
var languageLearning = $('#language-learning');
var levelLearning = $('#language-level');
if (languageLearning != null && levelLearning != null) {
ReactDOM.render(
<LanguageChip
language={languageLearning.val()}
level={levelLearning.val()}
/>,
document.getElementById('language-chips')
);
languageLearning.select2('val', '');
levelLearning.select2('val', '');
}
})
I didn't realise that when using React.DOM, "Any existing DOM elements inside are replaced when first called." This means when adding a second chip, the first is removed. I want users to be able to have multiple chips.
How can I do this?
I don't know if you've got a good reason to not add the form used to create a tag on your component, but it would be much simpler if you could.
Then you just have to add your tags on an array and display them with your LanguageChip component.
I've made an example here: https://jsfiddle.net/snahedis/69z2wepo/28193/
I don't know what's your level of understanding of React so if something isn't clear let me know :)
Edit: the same example inside a preexistent form:
https://jsfiddle.net/snahedis/69z2wepo/28217/
You need to use array to store multiple chips data. Take a look to this simplified example: http://output.jsbin.com/seficu
var LanguageChips = React.createClass({
render: function() {
return (
<div>
{
(this.props.chipsArray).map(function(chip, index) {
return <LanguageChip
key={index}
language={chip.languageLearning}
level={chip.levelLearning}
/>
})
}
</div>
);
}
});
var LanguageChip = React.createClass({
render: function() {
return (
<div className="language chip" data-lang={this.props.language} data-lang-level={this.props.level}>
{this.props.language} ({this.props.level})
<i className="material-icons"></i>
</div>
);
}
});
var chipsArray = [];
document.getElementById('add-language').addEventListener("click", function() {
var languageLearning = 'test1';
var levelLearning = 'test2';
if (languageLearning != null && levelLearning != null) {
chipsArray.push({
languageLearning: languageLearning,
levelLearning: levelLearning
});
ReactDOM.render(
<LanguageChips chipsArray={chipsArray} />,
document.getElementById('language-chips')
);
}
})

Mixin functions only work in render()

For some reason, it appears that mixin functions in my code only work properly in render() function. It could be that I'm not calling them in the right manner outside of the render(), but shouldn't it be exactly the same way?
This way everything works fine (but I can't stick with this since I have to add some extra stuff to click handling, at the same time not altering the mixin):
var Row = React.createClass({
mixins: [someMixin]
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.order(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.noOrder(this.props.name, this.props.else);
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
But when I do the obvious and include the mixin functions in another function to handle the click - like this - everything fails and even 'test' is not printed in the console:
var Row = React.createClass({
mixins: [someMixin]
},
handleOrderClick(type) {
console.log('test');
if (type == 'order') {
this.order(this.props.name, this.props.else);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else);
}
},
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick('order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick('no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
});
EDIT:
order and noOrder functions look like this:
order: function (name, else) {
return function (event) {
event.preventDefault();
var term = name + '&&ยค%' + else;
Order.order(name, else, period, function (data) {
if (term === (global || window).MAIN_NAME + '.' + (global || window).MAIN) {
$(window).trigger('Name:update');
}
}.bind(this));
}.bind(this);
},
noOrder: function (name, else) {
return function (event) {
event.preventDefault();
if (!this.state.transferModalOpen) {
this.toggleTransferModal();
}
}.bind(this);
}
In order to use this.setState in handleOrderClick you'll have to use the bind method in your render method. Therefore handleOrderClick will become:
handleOrderClick(type, event) {
this.setState({foo: 'bar'});
if (type == 'order') {
this.order(this.props.name, this.props.else)(event);
} else if (type == 'no-order') {
this.noOrder(this.props.name, this.props.else)(event);
}
},
and your render method becomes:
render: function () {
var clickHandler = null;
var btn = null;
if (firstCase) {
clickHandler = this.handleOrderClick.bind(this, 'order');
btn = (<a href="" onClick={clickHandler}>Order</a>);
} else if (secondCase) {
clickHandler = this.handleOrderClick(this, 'no-order');
btn = (<a href="" onClick={clickHandler}>No order</a>);
}
return (
<div>
{btn}
</div>
);
}
You'll notice that the functions that are returned by this.order and this.noOrder are no longer returned by handleOrderClick, but are instead executed immediately. This should provide the effect you desire.
I've put the code in your example into a jsfiddle and it now seems to be working correctly. I've had to change the prop 'else' to 'alt' because 'else' is a reserved word. I've also just applied the mixin to the class directly for simplicity. I have simpilfied the order and noOrder functions as I don't have access to the Order object and we are only interested in them firing at the correct time. I've also added a second button that you can click to flip the cases so the other button is rendered, causing the component to render again. I've added a label that will display which function had been called when the button was last pressed.
for reference, you can find more information about the bind method here.
Hope this helps ^_^

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'.

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

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);

Categories

Resources