Two events on a single button in react - javascript

I'm trying to create a button which can have two events on a single button so that I can add and remove a marker, I can do it with jQuery but can't work it out in react.
var Button = React.createClass({
getInitialState() {
return {
name: 'add marker'
};
},
render: function() {
return <button type="button" onClick={this.onClick}>{this.state.name}</button>
},
onClick: function(ev) {
// event 1
alert('marker added');
// event 2
alert('remove marker');
this.setState({name:'markert removed'})
}
});
http://jsfiddle.net/zidski/5z3f7zL4/1/

Assuming you want to use the same button to add/remove a marker, then you can do something like this:
var Button = React.createClass({
getInitialState() {
return {
marker: false
};
},
onClick: function() {
this.setState({
marker: !this.state.marker
});
},
render: function() {
return <button type="button" onClick={this.onClick}>{this.state.marker ? 'Add marker' : 'Remove marker'}</button>
}
});
Store a boolean value and update that when clicking the button. You can manage the buttons text based off that boolean.

you can access to the element with event.target,
if you would set the value of the button to something like 'add' or 'remove' you can capture it via event.target.value
for ex.
handleClick(event) {
let actionType = event.target.value; // 'add' or 'remove', now you can control the flow with switch case or regular if's */
}
render(){
return (<button value='add' onClick={this.handleClick}>{name}</button>)
}

Related

React pass all events to child component

Is it possible in React to pass all event to child element.
as an example I've got a custom Button class, that (simplified) looks something like this:
class Button extends Component {
constructor (props) {
super(props);
this.onClick = this.onClick.bind(this);
}
/* .... */
onClick (ev) {
const { disabled, onClick } = this.props;
if (!disabled) {
onClick(ev);
}
}
render () {
const {
children,
disabled,
type
} = this.props;
return (
<button
disabled={disabled}
onClick={this.onClick}
ref="button"
type={type}
>{children}</button>
}
}
I don't know what events i may want to use in the future (onMouseDown, onMouseUp, onBlur, onKeyDown, onTouchStart, and so on...)
Is it possible to pass all possible events to the button element without writing out a prop for every possible event?
adding {...this.props} to the button element is not what I want because it passes all props and some props (like className which is omitted in this example) should not be passed directly.
I thought about cloning the props object and deleting the props which should not be passed directly but this feels like a hack. Does anybody know a cleaner way?
I've written a function to iterate over the props and filter out all properties starting with 'on' this is the closest I've come so far. In case it helps anyone else:
/* helpers.js */
export function filterEvents (props, ignore = []) {
let events = {};
for (let property in props) {
if (props.hasOwnProperty(property)) {
if (property.startsWith('on') && ignore.indexOf(property) === -1) {
events[property] = props[property];
}
}
}
return events;
}
/* Tests for the filterEvents */
import { expect } from 'chai';
import { filterEvents } from './helpers';
describe('filterEvents', () => {
const props = {
className: 'someClass',
disabled: true,
onBlur: 'onBlur',
onClick: 'onClick',
onMouseDown: 'onMouseDown',
onMouseUp: 'onMouseUp'
};
it('only returns keys starting with on', () => {
const expected = {
onBlur: 'onBlur',
onClick: 'onClick',
onMouseDown: 'onMouseDown',
onMouseUp: 'onMouseUp'
};
expect(filterEvents(props)).to.deep.equal(expected);
});
it('only returns keys starting with on minus the ones in the ignore array', () => {
const expected = {
onBlur: 'onBlur',
onMouseUp: 'onMouseUp'
};
const ignore = ['onClick', 'onMouseDown'];
expect(filterEvents(props, ignore)).to.deep.equal(expected);
});
});
/* Using the function inside a component */
import { filterEvents } from './helpers'; //at the top of the components file
//Inside the render method:
const events = filterEvents(this.props, ['onClick']); //don't include onClick it's handled like the questions example
return (
<button
disabled={this.props.disabled}
onClick={this.onClick}
{...events}
>
{this.props.children}
</button>
);
I've taken Barry127's answer and added all the event handlers from React and put them in an object.
const acceptedEventHandlersForComponentValidationFunction = {
clipBoard: [
"onCopy",
"onCut",
"onPaste",
"onCopyCapture",
"onCutCapture",
"onPasteCapture"
],
composition: [
"onCompositionEnd",
"onCompositionStart",
"onCompositionUpdate",
"onCompositionEndCapture",
"onCompositionStartCapture",
"onCompositionUpdateCapture"
],
keyboard: [
"onKeyDown",
"onKeyPress",
"onKeyUp",
"onKeyDownCapture",
"onKeyPressCapture",
"onKeyUpCapture"
],
focus: ["onFocus", "onBlur", "onFocusCapture", "onBlurCapture"],
form: [
"onChange",
"onInput",
"onInvalid",
"onReset",
"onSubmit",
"onChangeCapture",
"onInputCapture",
"onInvalidCapture",
"onResetCapture",
"onSubmitCapture"
],
generic: ["onError", "onLoad", "onErrorCapture", "onLoadCapture"],
mouse: [
"onClick",
"onContextMenu",
"onDoubleClick",
"onDrag",
"onDragEnd",
"onDragEnter",
"onDragExit",
"onDragLeave",
"onDragOver",
"onDragStart",
"onDrop",
"onMouseDown",
"onMouseEnter",
"onMouseLeave",
"onMouseMove",
"onMouseOut",
"onMouseOver",
"onMouseUp",
"onClickCapture",
"onContextMenuCapture",
"onDoubleClickCapture",
"onDragCapture",
"onDragEndCapture",
"onDragEnterCapture",
"onDragExitCapture",
"onDragLeaveCapture",
"onDragOverCapture",
"onDragStartCapture",
"onDropCapture",
"onMouseDownCapture",
"onMouseMoveCapture",
"onMouseOutCapture",
"onMouseOverCapture",
"onMouseUpCapture"
],
pointer: [
"onPointerDown",
"onPointerMove",
"onPointerUp",
"onPointerCancel",
"onGotPointerCapture",
"onLostPointerCapture",
"onPointerEnter",
"onPointerLeave",
"onPointerOver",
"onPointerOut",
"onPointerDownCapture",
"onPointerMoveCapture",
"onPointerUpCapture",
"onPointerCancelCapture",
"onGotPointerCaptureCapture",
"onLostPointerCaptureCapture",
"onPointerOverCapture",
"onPointerOutCapture"
],
selection: ["onSelect", "onSelectCapture"],
touch: [
"onTouchCancel",
"onTouchEnd",
"onTouchMove",
"onTouchStart",
"onTouchCancelCapture",
"onTouchEndCapture",
"onTouchMoveCapture",
"onTouchStartCapture"
],
ui: ["onScroll", "onScrollCapture"],
wheel: ["onWheel", "onWheelCapture"],
media: [
"onAbort",
"onCanPlay",
"onCanPlayThrough",
"onDurationChange",
"onEmptied",
"onEncrypted",
"onEnded",
"onError",
"onLoadedData",
"onLoadedMetadata",
"onLoadStart",
"onPause",
"onPlay",
"onPlaying",
"onProgress",
"onRateChange",
"onSeeked",
"onSeeking",
"onStalled",
"onSuspend",
"onTimeUpdate",
"onVolumeChange",
"onWaiting",
"onAbortCapture",
"onCanPlayCapture",
"onCanPlayThroughCapture",
"onDurationChangeCapture",
"onEmptiedCapture",
"onEncryptedCapture",
"onEndedCapture",
"onErrorCapture",
"onLoadedDataCapture",
"onLoadedMetadataCapture",
"onLoadStartCapture",
"onPauseCapture",
"onPlayCapture",
"onPlayingCapture",
"onProgressCapture",
"onRateChangeCapture",
"onSeekedCapture",
"onSeekingCapture",
"onStalledCapture",
"onSuspendCapture",
"onTimeUpdateCapture",
"onVolumeChangeCapture",
"onWaitingCapture"
],
image: ["onLoad", "onError", "onLoadCapture", "onErrorCapture"],
animation: [
"onAnimationStart",
"onAnimationEnd",
"onAnimationIteration",
"onAnimationStartCapture",
"onAnimationEndCapture",
"onAnimationIterationCapture"
],
transition: ["onTransitionEnd", "onTransitionEndCapture"],
other: ["onToggle", "onToggleCapture"]
}
/*
- Component props event handler vilidation
Return all valid events to be used on a component
{
acceptedEventHandlerTypes: [
"${event handler type}"
],
eventHandlers: {
${event}: "${callback function}" // ${event} can contain "Capture" at the end to register the event handler for the capture phase
}
}
*/
const validateComponentPropsEventHandlers = (
acceptedEventHandlerTypes,
eventHandlers = {}
) => {
if (Object.keys(eventHandlers).length == 0) {
return {}
}
// Fill eventsForSpecifiedType with only the required events
let eventsForSpecifiedType = {}
let eventsCount = 0
for (const eventHandlerType in acceptedEventHandlerTypes) {
if (
acceptedEventHandlerTypes[eventHandlerType] in
acceptedEventHandlersForComponentValidationFunction
) {
const newEvents =
acceptedEventHandlersForComponentValidationFunction[
acceptedEventHandlerTypes[eventHandlerType]
]
eventsForSpecifiedType[
acceptedEventHandlerTypes[eventHandlerType]
] = newEvents
eventsCount += newEvents.length
}
}
// Fill events
let events = {}
let eventsCountCheck = 0
const checkIfEventsCountHasBeenReached = () =>
eventsCountCheck == eventsCount
for (const eventHandler in eventHandlers) {
if (checkIfEventsCountHasBeenReached()) {
return events
}
// Append event handler to events object if it is recognised
Object.values(eventsForSpecifiedType).forEach(
(EVENT_HANDLERS) => {
if (
EVENT_HANDLERS.includes(eventHandler) &&
!(eventHandler in events)
) {
events[eventHandler] = eventHandlers[eventHandler]
eventsCountCheck += 1
}
if (checkIfEventsCountHasBeenReached()) {
return events
}
}
)
}
return events
}
// Usage
const test = () => {console.log("test")}
const events = validateComponentPropsEventHandlers(["mouse"], { onClick: test })
console.log(events)
// <button {...events}>Button</button>

Testing for a button's disabled state in React

I have a simple component that I want to test using React and ReactUtils.
var TextConfirmButton = React.createClass({
getInitialState: function() {
return {
inputText: '',
confirmText: this.props.confirmText,
buttonEnabled: false,
inputEnabled: true
}
},
handleChange: function(event) {
this.setState({ inputText: event.target.value });
},
handleConfirm: function() {
this.props.onConfirmClick();
// When user clicks the confirm button, disable both the input and button.
this.setState({ buttonEnabled: false, inputEnabled: false });
},
render: function() {
return (
<div>
<input onChange={this.handleChange} disabled={!this.state.inputEnabled} type='text' ref='text' placeholder={this.state.confirmText} />
<button onClick={this.handleConfirm} disabled={this.state.inputText !== this.state.confirmText} className='btn btn-danger'>Delete</button>
</div>
)
}
})
Is there a way to test for a button's disabled state?
I've attempted:
var TestUtils = React.addons.TestUtils;
describe('TextConfirmButton', function () {
it('starts with confirm button disabled', function () {
var onConfirmClick = function() {
console.log("Confirm click");
}
var textConfirmButton = TestUtils.renderIntoDocument(
<TextConfirmButton confirmText="example" onConfirmClick={this.onConfirmClick} />
);
var textConfirmButtonNode = ReactDOM.findDOMNode(textConfirmButton);
expect(textConfirmButtonNode.disabled).toEqual('disabled');
});
});
But the test fails, with the error: textConfirmButtonNode.disabled undefined. So .disabled is obviously the wrong way to go about this.
Any suggestions?
You need to use the TestUtils#findRenderedDOMComponentWithTag in order to be able to query the DOM generated by TestUtils.
var textConfirmButtonNode =
TestUtils.findRenderedDOMComponentWithTag(textConfirmButton, 'button');
expect(textConfirmButtonNode.disabled).toEqual(true);
textConfirmButtonNode references the outermost div in your render() function. Unless it has an attribute of disabled, it isn't surprising that it is returning undefined.
My guess is that you were looking for a DOM node that references the actual button.
var textConfirmButtonNode = ReactDOM.findDOMNode(textConfirmButton);
var renderedButtonNode = textConfirmButtonNode.childNodes[1];
expect(renderedButtonNode.disabled).toEqual('disabled');

React.JS this.state is undefined

I currently have this component in React.JS which shows all the Images passed to it in an array and onMouseOver it shows a button below. I planed on using setState to check the variable hover if is true or false and toggle the button of that image accordingly however I keep getting the following error:
Uncaught TypeError: Cannot read property 'state' of undefined
var ImageList = React.createClass({
getInitialState: function () {
return this.state = { hover: false };
},
getComponent: function(index){
console.log(index);
if (confirm('Are you sure you want to delete this image?')) {
// Save it!
} else {
// Do nothing!
}
},
mouseOver: function () {
this.setState({hover: true});
console.log(1);
},
mouseOut: function () {
this.setState({hover: false});
console.log(2);
},
render: function() {
var results = this.props.data,
that = this;
return (
<ul className="small-block-grid-2 large-block-grid-4">
{results.map(function(result) {
return(
<li key={result.id} onMouseOver={that.mouseOver} onMouseOut={that.mouseOut} ><img className="th" alt="Embedded Image" src={"data:" + result.type + ";" + "base64," + result.image} /> <button onClick={that.getComponent.bind(that, result.patientproblemimageid)} className={(this.state.hover) ? 'button round button-center btshow' : 'button round button-center bthide'}>Delete Image</button></li>
)
})}
</ul>
);
}
});
You get the error because you're storing the reference to this in a that variable which you're using to reference your event handlers, but you're NOT using it in the ternary expression to determine the className for the button element.
your code:
<button
onClick={ that.getComponent.bind(that, result.patientproblemimageid) }
className={ (this.state.hover) ? // this should be that
'button round button-center btshow' :
'button round button-center bthide'}>Delete Image
</button>
When you change this.state.hover to that.state.hover you won't get the error.
On a side note, instead of storing the reference to this in a that variable you can simple pass a context parameter to the map() method.
results.map(function (result) {
//
}, this);
In ES5 format you cannot set this.state directly
var ImageList = React.createClass({
getInitialState: function () {
return { hover: false };
},
render : function(){
return(<p>...</p>);
});
However with new ES6 syntax you can essentially manage this:
class ImageList extends React.Component{
constructor (props) {
super(props);
this.state = {hover : false};
}
render (){ ... }
}

ReactJs - Why wont child event change state of parent?

I have a created a menu component in reactjs. As you can see, the parent component has a method called "handleClick which toggles the "open state" of the menu, opening and closing it accordingly.
Now, I am trying to pass a click event from the child component "MenuItem" (which is a link in the menu) to the parent "Menu" component so that when one of the menu items is clicked the menu closes.
I have tried to do this in a number of ways. At the moment, I have bound the click event of each "MenuItem" in "MenuList" (the list of MenuItems) to a prop called "whenClicked" and then bound "whenClicked" to the "handleClick" method of "Menu".
The problem is that this seems to have no effect on "Menu". Neither the React tool in Chrome, nor the regular dev console are giving me any errors but the menu does not close when I click one of the MenuItems. The React tool in Chrome allows me to view the virtual DOM and I can see that all of the onClick functions are defined.
Below is the code. As you can see, I am using the same methodology to pass a click event from a different component ("MenuToggle") to "Menu". Oddly enough this works fine and clicking on the toggle button changes the state of "Menu" successfully and opens and closes the menu. I am using the "react-scroll" module for "MenuItem" so maybe that is the issue. Any light anyone can shed on this would be helpful and I would love to know what I am doing incorrectly!
var Menu = React.createClass({
getInitialState: function() {
return {open: false, mobi: false}
},
handleClick: function() {
this.setState({open: !this.state.open})
},
closeOnMobiScroll: function() {
/*
if(this.state.mobi === false) {
this.setState({open: false})
}
*/
},
updateDimensions: function() {
$(window).width() >= 767 ? this.setState({mobi: true}) : this.setState({mobi: false});
},
componentWillMount: function() {
this.updateDimensions();
},
componentDidMount: function() {
$(window).on("resize", this.updateDimensions);
},
componentWillUnmount: function() {
$(window).on("resize", this.updateDimensions);
},
render: function() {
return (
<div id="menu" className={(this.state.open ? 'open' : '')} >
<div id="menu-inner-wrap">
<MenuTitle />
<MenuToggle whenClicked={this.handleClick}/>
<MenuList whenClicked={this.handleClick}/>
</div>
</div>
)
}
});
module.exports = Menu;
var MenuItem = React.createClass({
render: function() {
return (
<li className="menu-link">
<Link to={this.props.menuLink} spy={true} smooth={true} duration={500}>
<i className={this.props.icon}></i>
<span className="menu-link-text">{this.props.menuTitle}</span>
</Link>
</li>
);
}
});
var MenuList = React.createClass({
getInitialState: function() {
return {data: []}
},
componentWillMount: function() {
$.ajax({
url: 'http://10.0.0.97:8888/public-code/React.cv/data/data.json',
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, error) {
var err = JSON.parse(xhr.responseText);
console.log(err.Message);
}
});
},
render: function() {
var list = this.state.data.map(function(menuItemProps) {
return <MenuItem onClick={this.props.whenClicked} {...menuItemProps} key={menuItemProps.id} />
}.bind(this));
return (
<ul id="menu-list">
{list}
</ul>
)
}
});
It seems like you still need to bind the onClick to something that the DOM will handle. Adding an onClick attribute to MenuItem allows you to have a prop inside MenuItem, but you still need to bind it:
var MenuItem = React.createClass({
render: function() {
return (
<li className="menu-link" onClick={this.props.onClick}>
<Link to={this.props.menuLink} spy={true} smooth={true} duration={500}>
<i className={this.props.icon}></i>
<span className="menu-link-text">{this.props.menuTitle}</span>
</Link>
</li>
);
}
});
}
In the above example, onClick is added to the li:
<li className="menu-link" onClick={this.props.onClick}>
The best example in the documentation of behaviour like this is in Expose Component Functions.
In that example you can see that the Todo child component finally binds to div and then bubbles up in a similar way:
var Todo = React.createClass({
render: function() {
return <div onClick={this.props.onClick}>{this.props.title}</div>;
},
//this component will be accessed by the parent through the `ref` attribute
animate: function() {
console.log('Pretend %s is animating', this.props.title);
}
});

ReactJS: onClick change element

I've just started learning React and have a question.
I want to do the following:
If a user clicks on a paragraph I want to change the element to an input field that has the contents of the paragraph prefilled.
(The end goal is direct editing if the user has certain privileges)
I'm come this far but am totally at a loss.
var AppHeader = React.createClass({
editSlogan : function(){
return (
<input type="text" value={this.props.slogan} onChange={this.saveEdit}/>
)
},
saveEdit : function(){
// ajax to server
},
render: function(){
return (
<header>
<div className="container-fluid">
<div className="row">
<div className="col-md-12">
<h1>{this.props.name}</h1>
<p onClick={this.editSlogan}>{this.props.slogan}</p>
</div>
</div>
</div>
</header>
);
}
});
How can I override the render from the editSlogan function?
If I understand your questions correctly, you want to render a different element in case of an "onClick" event.
This is a great use case for react states.
Take the following example
React.createClass({
getInitialState : function() {
return { showMe : false };
},
onClick : function() {
this.setState({ showMe : true} );
},
render : function() {
if(this.state.showMe) {
return (<div> one div </div>);
} else {
return (<a onClick={this.onClick}> press me </a>);
}
}
})
This will change the components state, and makes React render the div instead of the a-tag. When a components state is altered(using the setState method), React calculates if it needs to rerender itself, and in that case, which parts of the component it needs to rerender.
More about states
https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html
You can solve it a little bit more clear way:
class EditableLabel extends React.Component {
constructor(props) {
super(props);
this.state = {
text: props.value,
editing: false
};
this.initEditor();
this.edit = this.edit.bind(this);
this.save = this.save.bind(this);
}
initEditor() {
this.editor = <input type="text" defaultValue={this.state.text} onKeyPress={(event) => {
const key = event.which || event.keyCode;
if (key === 13) { //enter key
this.save(event.target.value)
}
}} autoFocus={true}/>;
}
edit() {
this.setState({
text: this.state.text,
editing: true
})
};
save(value) {
this.setState({
text: value,
editing: false
})
};
componentDidUpdate() {
this.initEditor();
}
render() {
return this.state.editing ?
this.editor
: <p onClick={this.edit}>{this.state.text}</p>
}
}
//and use it like <EditableLabel value={"any external value"}/>;

Categories

Resources