I am trying to show/hide a component based on its state and I want to change it on a click in a 3rd component.
//navbar
export class NavigationBar extends React.Component {
constructor(props) {
super(props);
this.state = {
showNotification: false,
}
}
handleNotification = () => this.setState({
showNotification: !this.state.showNotification,
});
{ this.state.showNotification ? <Outside><Notifications /></Outside> : null}
//outside component, responsible for detect if a click happened outside it.
export default class Outside extends Component {
constructor(props) {
super(props);
this.setWrapperRef = this.setWrapperRef.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
}
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside)
}
setWrapperRef(node) {
this.wrapperRef = node;
}
handleClickOutside(event) {
if(this.wrapperRef && !this.wrapperRef.contains(event.target)) {
console.log("clicked outside notifications");
this.setState({
showNotification: false
})
}
}
render() {
return (
<div ref={this.setWrapperRef}>
{this.props.children}
</div>
)
}
}
Outside.propTypes = {
children: PropTypes.element.isRequired
}
My doubt is how can I change the state in navbar based on the event that is being detected inside Outside component ?
In parent, you need downward to Outside a event handler:
<Outside toggleNofitication={this.handleNotification}><Notifications /></Outside>
and in Outside, just call toggleNofitication when event fired:
handleClickOutside = () => {
// ...
this.props.toggleNofitication()
}
Related
I have two components (Parent component & Child component) in my react app. I have two button clicks in my child component and I need to pass two props to the parent component. I use the code as follows.
The problem is, I can't include both methods in the parent component's element, but I need to. How can I use both edituser and deleteuser functions in the parent component?
Child component:
class EnhancedTable extends React.Component {
constructor(props){
super(props);
this.state = {
userID: 10
};
this.editByUserId = this.sendUserId.bind(this);
this.DeleteByUserId = this.sendUserId.bind(this);
}
editByUserId() {
this.props.onClick(this.state.userID);
}
DeleteByUserId() {
this.props.onClick(this.state.userID);
}
render() {
return (
<button onClick={this.sendUserId}>
<BorderColorIcon onClick={this.editUserById} className="action margin-r" />
<DeleteIcon onClick={this.deleteUserById} className="action margin-r" />
</button>
)
}
}
Parent component:
Import EnhancedTable from './EnhancedTable';
class Users extends Component {
constructor(props) {
super(props);
this.state = {
userID: null
};
this.editUser = this.editUser.bind(this);
this.deleteUser = this.deleteUser.bind(this);
}
editUser(idd) {
this.setState({
userID : idd
})
console.log("User Edited");
}
deleteUser(idd) {
this.setState({
userID : idd
})
console.log("User Deleted");
}
render() {
return(
<EnhancedTable onClick = {(e)=>{this.editUser; this.deleteUser;}}/>
)
}
}
You missed your ()
<EnhancedTable onClick = {(e)=>{this.editUser(); this.deleteUser();}}/>
You are doing it right in
<EnhancedTable onClick = {(e)=>{this.editUser; this.deleteUser;}}/>
A minor change is needed:
<EnhancedTable onClick = {(e)=>{this.editUser(e); this.deleteUser(e);}}/>
A quick reference for what changed here:
let x = () => {
console.log('hello');
}
x; // This simply does nothing as it is just a reference to the function
x(); // This instead invokes the function
I have the following rather simple React Class:
import React from "react"
export default class DoubleClick extends React.Component {
constructor(props) {
super(props);
this.state = {
};
this.handleClick = this.handleClick.bind(this);
this.handleDoubleClick = this.handleDoubleClick.bind(this);
}
handleClick() {
console.log("Click");
}
handleDoubleClick() {
console.log("Double Click");
}
render() {
return (
<div style={{backgroundColor: 'pink'}}>
<div onClick={this.handleClick}>
<span onDoubleClick={this.handleDoubleClick}> Hello </span>
<span onDoubleClick={this.handleDoubleClick}> world. </span>
</div>
</div>
);
}
}
When somebody single-clicks on the outer div, I want to call handleClick, when somebody double-clicks on any of the inner spans I want to call handleDoubleClick, but not the handleClick for the outer div as well.
However, whenever I double-click, handleDoubleClick() is called, but handleClick is also called, namely twice.
I would like to call handleClick() only when I click, but do not double-click - is this possible?
I have a HOC that I use to approximate what you're looking for:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class DoubleClick extends Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.onDoubleClick = this.onDoubleClick.bind(this);
this.timeout = null;
}
onClick(e) {
e.preventDefault();
if(this.timeout === null) {
this.timeout = window.setTimeout(() => {
this.timeout = null;
this.props.onClick();
}, 300);
}
}
onDoubleClick(e) {
e.preventDefault();
window.clearTimeout(this.timeout);
this.timeout = null;
this.props.onDoubleClick();
}
render() {
const { onClick, onDoubleClick, children, ...childProps } = this.props;
const props = Object.assign(childProps, { onClick: this.onClick, onDoubleClick: this.onDoubleClick });
return React.cloneElement(children, props);
}
}
DoubleClick.propTypes = {
onClick: PropTypes.func.isRequired,
onDoubleClick: PropTypes.func.isRequired,
children: PropTypes.element.isRequired,
};
Used like this:
<DoubleClick onClick={clickHandler} onDoubleClick={doubleClickHandler}>
<button>Click or double click me</button>
</DoubleClick>
When the first click is received, it sets a 300ms timeout. If no second click is received within 300ms, the function in the onClick prop will be invoked. If a second click is received within 300ms, the function in the onDoubleClick prop will be invoked, and the timeout is canceled so the onClick does not fire.
Needless to say, this is an imperfect approximation, but I've found it to be a sufficiently satisfying user experience in practice.
Like others said, it's not possible to do a click event on the parent with a doubleClick on the children without a timeout (I think).
If you can sacrifice the UX part of the 500ms until the click is recognized as a non-doubleclick you can work with timeout.
constructor(props) {
super(props);
this.state = {
};
this.handleClick = this.handleClick.bind(this);
this.handleDoubleClick = this.handleDoubleClick.bind(this);
// hack doubleclick
this.doubleClickTimeout = 500; // default doubleclick timeout
this.clickedTimeout = null;
}
handleClick(ev) {
if (!this.clickedTimeout) {
this.clickedTimeout = setTimeout(() => {
this.clickedTimeout = null;
// do your stuff here
console.log('Clicked');
}, this.doubleClickTimeout);
}
}
handleDoubleClick() {
clearTimeout(this.clickedTimeout);
this.clickedTimeout = null;
console.log("Double Click");
}
render() {
return (
<div style={{backgroundColor: 'pink'}}>
<div onClick={this.handleClick}>
<span onDoubleClick={this.handleDoubleClick}> Hello </span>
<span onDoubleClick={this.handleDoubleClick}> world. </span>
</div>
</div>
);
}
I am having a Parent component (BookApplication) and a child component (SearchBox) in React. The SearchBox has an input field, and should give the input back to the parent for handling the event. That is working fine, but when i am back in the parent component in the method handleSearch the this.state... is undefined.
TypeError: Cannot read property 'books' of undefined
But searchInput has the value it should have.
But i need the books from this.state.books again :/
I understand that in the method handleSearch i am working in it's scope, so this.... is the context of handleSearch... but how do i get the arguments of it's component the BookApplication again?
I am still learning javascript, and i thought this shouldnt be a problem, because a function can always use the variables of it's parent object?
class BookApplication extends React.Component {
constructor() {
super();
this.state = {books: []};
}
componentDidMount() {
$.get(PATH, function (result) {
this.setState({
books: result
});
}.bind(this));
}
handleSearch(searchInput) {
//Sort the books list
var sortedList = [];
this.state.books.map(
function (currentBook) {
currentBook.keys().forEach(
function (key, pos) {
if (key.contains(searchInput)) {
sortedList.push(currentBook)
}
}
)
}
);
}
render() {
return (
<div>
<SearchBox onSearch={this.handleSearch}/>
<div className="book-list">
{this.state.books.map(function (currentBook) {
return <Book book={currentBook} key={currentBook.id}/>;
}) }
</div>
</div>
);
}
Here also my SearchBox:
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.state = {searchFieldInput: ''};
this.handleSearchChange = this.handleSearchChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSearchChange(event) {
this.setState({searchFieldInput: event.target.value});
}
handleSubmit(e) {
//Prevent the browser's defeault action of submitting the form
e.preventDefault();
var searchFieldInput = this.state.searchFieldInput.trim();
//Call the passed callback function
this.props.onSearch({searchFieldInput: searchFieldInput});
}
render() {
return (
<div className="book-search">
<input
type="text"
value={this.state.searchFieldInput}
onChange={this.handleSearchChange}
placeholder="Search..."
className="search-bar"
/>
<button onClick={this.handleSubmit} className="search-button">Search</button>
</div>
);
}
}
If your question is how to get parent's context from child component, then try
class ParentComponent extends React.Component {
...
...
clickHandler(event){}
render(){
<ChildComponent parent={this}/>
}
}
class ChildComponent extends React.Component {
constructor(props){
super(props);
}
render(){
let parent = this.props.parent;
return <button onClick={parent.clickHandler}></button>
}
}
And you will get an error here
componentDidMount() {
$.get(PATH, function (result) {
this.setState({
books: result
});
}.bind(this));
}
Because this in the callback function not referred to your Component's context. You should keep component's context in variable
componentDidMount() {
let self = this;
$.get(PATH, function (result) {
self.setState({
books: result
});
}.bind(this));
}
Finally decision is
constructor(props) {
super(props);
this.state = {books: []};
//add the following line into your code
this.handleSearch = this.handleSearch.bind(this);
}
I have a button in render(), and I want it's onClick() to set the state. I know you shouldn't be setting the state in render() because it causes an infinite loop, so how should I go about this?
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
}
onCancel() {
this.setState({ edit: false, value: this.props.value });
}
onClick() {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.onClick}>
BUTTON
</div>
);
}
Updated to show what the code I'm trying now and the warning I'm getting thousands of times:
Warning: setState(...): Cannot update during an existing state transition (such as
within `render` or another component's constructor). Render methods should be a
pure function of props and state; constructor side-effects are an anti-pattern, but
can be moved to `componentWillMount`.
warning # warning.js?0260:44
getInternalInstanceReadyForUpdate # ReactUpdateQueue.js?fd2c:51
enqueueSetState # ReactUpdateQueue.js?fd2c:192
ReactComponent.setState # ReactComponent.js?702a:67
onCancel # mybutton.js?9f63:94
onClick # mybutton.js?9f63:98
render # mybutton.js?
...
Not really sure what you want to do since the previous answers didn't solve the issue. So if you provide some more information it might get easier.
But here is my take on it:
getInitialState() {
return (
edit: true
);
}
handleEdit() {
this.setState({edit: true});
}
handelCancel() {
this.setState({edit: false});
}
render() {
var button = <button onClick={this.handelEdit}>Edit</button>
if(this.state.edit === true) {
button = <button onClick={this.handelCancel}>Cancel</button>
}
return (
<div>
{button}
</div>
);
}
To set the state for your use case you need to set the state somewhere but I wouldn't do it this way. I would bind a function to the onClick event.
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
this.handleButtonClick = this.onClick.bind(this);
}
onCancel() {
this.setState({ edit: false, value: this.props.value });
}
onClick() {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.handleButtonClick}>
BUTTON
</div>
);
}
Look here for more information
Try to make use of arrow functions to bind onBtnClick and onCancel function to the context and see if it solves your problem.
function initialState(props) {
return {
edit: false,
value: props.value,
};
}
export default class MyButton extends React.Component {
constructor(props) {
super(props);
this.state = initialState(props);
}
onCancel = ()=> {
this.setState({ edit: false, value: this.props.value });
}
onBtnClick = () => {
this.state.edit ? this.onCancel() : this.setState({ edit: true });
}
render() {
return (
<div onClick={this.onBtnClick}>
BUTTON
</div>
);
}
I would like to add Event onMouseEnter to component prop without wrapper HTML tag.
var tooltipText = this.props.children;
<span onMouseEnter={this.renderTooltip}>{tooltipText}</span>
Is it possible add this event to this.props.children, because I can't afford on the wrapper span.
Here is all component code:
class FastTooltip extends Component{
constructor(props){
super(props);
this.renderTooltip = this.renderTooltip.bind(this);
this.state = {
tooltip: false,
};
}
renderTooltip(){
this.setState({ tooltip: true });
}
render(){
var renderTooltip;
var { overlay, placement } = this.props;
var tooltipText = this.props.children;
if( this.state.tooltip ){
renderTooltip = <OverlayTrigger placement={placement} overlay={<Tooltip>{overlay}</Tooltip>}>
{tooltipText}
</OverlayTrigger>
} else {
renderTooltip = <span onMouseEnter={this.renderTooltip}>{tooltipText}</span>
}
return renderTooltip;
}
}
If I understand correctly, you could map your component's children and return a new element with an event handler. Does something like this get you on the right track?
class FastTooltip extends Component {
render() {
return React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {
onMouseEnter: () => { console.log('onMouseEnter'); }
})
});
}
}