So I've set up a file to setState() for onBlur and onFocus in the SocialPost.js file. But when I onClick a <div> in the SocialPostList.js (the parent) where it activates the parameterClicked() function in the SocialPost.js file, the <input> in SocialPost.js becomes blurred.
How do I make it so that the <button> onClick in SocialPostList.js does not take the focus() from the <input> in SocialPost.js?
I've tried e.preventDefault() and e.stopPropagation() without success. The files are below, any help would be appreciated!!!
SocialPostList.js
import React, { Component } from 'react'
import { graphql, gql } from 'react-apollo'
import SocialPost from './SocialPost'
class SocialPostList extends Component {
render() {
const PostListArray = () => {
return(
<div onClick={(e) => {e.preventDefault(); e.stopPropagation()}}>
{this.props.allParametersQuery.allParameters.map((parameter, index) => (
<div
key={index}
onClick={(e) => {e.preventDefault();e.stopPropagation();this.child.parameterClicked(parameter.param, parameter.id)}}
>{'{{' + parameter.param + '}}'}</div>
))}
</div>)
}
return (
<div>
...
<PostListArray />
{this.props.allSocialPostsQuery.allSocialPosts.map((socialPost, index) => (
<SocialPost
ref={instance => {this.child = instance}}
key={socialPost.id}
socialPost={socialPost}
index={index}
deleteSocialPost={this._handleDeleteSocialPost}
updateSocialPost={this._handleUpdateSocialPost}
allParametersQuery={this.props.allParametersQuery}/>
))}
...
</div>
)
}
}
const ALL_SOCIAL_POSTS_QUERY = gql`
query AllSocialPostsQuery {
allSocialPosts {
id
default
message
}}`
export default graphql(ALL_SOCIAL_POSTS_QUERY, {name: 'allSocialPostsQuery'})(SocialPostList)
SocialPost.js
import React, { Component } from 'react'
class SocialPost extends Component {
constructor(props) {
super(props)
this.state = {
message: this.props.socialPost.message,
focus: false
}
this._onBlur = this._onBlur.bind(this)
this._onFocus = this._onFocus.bind(this)
}
_onBlur() {
setTimeout(() => {
if (this.state.focus) {
this.setState({ focus: false });
}}, 0);
}
_onFocus() {
if (!this.state.focus) {
this.setState({ focus: true });
}
}
render() {
return (
<div className='socialpostbox mb1'>
<div className='flex'>
<input
onFocus={this._onFocus}
onBlur={this._onBlur}
type='text'
value={this.state.message}
onChange={(e) => { this.setState({ message: e.target.value})}}/>
</div>
</div>
)
}
parameterClicked = (parameterParam) =>{
if (!this.state.focus) return
let message = this.state.message
let newMessage = message.concat(' ' + parameterParam)
this.setState({ message: newMessage })
}
export default SocialPost
Well, I don't think that's a React thing. It appears the blur event fires before the onClick, so the latter cannot prevent the former, and I'd expect event.stopPropagation to stop events bubbling from child to parent, not the other way around. In other words, I don't know how to stop it.
In all fairness this behaviour is expected - clicking somewhere else makes you lose focus. That said, here and elsewhere a solution is presented where you set up a flag on mouse down. Then, when blur fires, if it encounters the 'click flag' it may abstain from producing effects and may even refocus back.
If you choose to refocus, it is trivial to save a reference to the button or input, or querySelecting it (it's not too late or anything like that). Just be cautious that it is all too easy to set focus traps or mess up navigation for screen readers when you mix focus and javascript.
Related
So I am trying to use onMouseOver on my React component like this
<CookieDetails
key={cookie.id}
name={cookie.name}
cost={cookie.cost}
value={cookie.cost}
numOwned={purchasedItems[cookie.id]}
onMouseOver={event => {
console.log('cookies');
if (numCookies < cookie.cost) {
alert('Not Enough Cookies');
return;
}
setPurchasedItems({
...purchasedItems,
[cookie.id]: purchasedItems[cookie.id] + 1,
});
setNumCookies(numCookies - cookie.cost);
console.log('purchased', purchasedItems[cookie.id], numCookies);
}}
/>;
and here is whats inside my component for now
import React from 'react';
const CookieDetails = ({ name, cost, value, numOwned }) => {
return (
<React.Fragment>
<div className="cookie-details-wrapper">
<h3>{name}</h3>
<p>Cost:{cost}</p>
<p>Value:{value}</p>
<p>Owned:{numOwned}</p>
</div>
</React.Fragment>
);
};
export default CookieDetails;
However when I mouse over my component, nothing happens. Doesn't event console log cookies. Any help would be appreciated
CookieDetails isn't a DOM element (so it doesn't have native handling for events) it is a component you wrote. You don't do anything with the onMouseOver prop.
You need to read that prop and attach it to a DOM element (like the div).
const CookieDetails = ({ name, cost, value, numOwned, onMouseOver }) => {
return (
<React.Fragment>
<div onMouseOver={onMouseOver} className="cookie-details-wrapper">
So here onMouseOver is a prop that you are passing to children. You need to trigger onMouseOver on children
<CookieDetails
key={cookie.id}
name={cookie.name}
cost={cookie.cost}
value={cookie.cost}
numOwned={purchasedItems[cookie.id]}
onMouseOver={event => {
console.log('cookies');
if (numCookies < cookie.cost) {
alert('Not Enough Cookies');
return;
}
setPurchasedItems({
...purchasedItems,
[cookie.id]: purchasedItems[cookie.id] + 1,
});
setNumCookies(numCookies - cookie.cost);
console.log('purchased', purchasedItems[cookie.id], numCookies);
}}
/>;
import React from 'react';
const CookieDetails = ({ name, cost, value, numOwned, onMouseOver }) => {
return (
<React.Fragment>
<div className="cookie-details-wrapper" onMouseOver={onMouseOver}>
<h3>{name}</h3>
<p>Cost:{cost}</p>
<p>Value:{value}</p>
<p>Owned:{numOwned}</p>
</div>
</React.Fragment>
);
};
export default CookieDetails;
In my react.js project I have parent and child components (Page like parent and Modal with input like child). I receive data in parent by ajax request and pass it for input from parent to child and fill the child state with it. Also in child component I have 2 buttons: Submit and Cancel. Conside the following code:
**Parent**
render() {
const { projectAux } = this.props;
return (
<ProjectNameFormModal
projectAux={projectAux}
onVisibleChange={e => this.onVisibleProjectNameFormModalChange(e)}
visible={this.state.editProjectNameModalVisible}
/>
)
}
**Child**
import React from 'react';
import {Button, Checkbox, Form, Input, List, Modal, Select} from "antd";
class ProjectNameFormModal extends React.Component{
constructor(props){
super(props);
this.state = {
projectAux: props.projectAux,
visible: props.visible
}
}
shouldComponentUpdate(nextProps, nextState, nextContext) {
if(this.state.projectAux != nextProps.projectAux){
this.setState({
projectAux: nextProps.projectAux,
visible: nextProps.visible
});
}
return true;
}
handleProjectNameInputChange = e => {
let current_state = this.state;
current_state.projectAux[e.target.name] = e.target.value;
this.setState(current_state);
}
handleCancelSubmitProjectNameUpdate = e => {
this.props.onVisibleChange(false);
}
handleSubmitProjectNameUpdate = e => {
console.log(' in handleSubmitProjectNameUpdate');
this.setState({...this.state, visible: false});
this.props.onVisibleChange(false);
}
render() {
return (
<Modal
title='Edit project Name'
visible={this.props.visible}
bodyStyle={{}}//height:"800px"
onSave={{}}
maskClosable={false}
onCancel={this.handleCancelSubmitProjectNameUpdate}
footer={[
<Button key="back" onClick={this.handleCancelSubmitProjectNameUpdate}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={this.handleSubmitProjectNameUpdate}>
Save
</Button>,
]}
>
<div>
<Input placeholder="ProjectName"
name="name"
onChange={this.handleProjectNameInputChange}
value={this.state.projectAux && (this.state.projectAux.name)}
/>
</div>
</Modal>
);
}
}
export default ProjectNameFormModal;
So the problem is when I enter some new data to input and then press Cancel button, I have also my Parent state updated to the new data which I definetely dont want to happen. When Cancel is pressed, no updates to the parent state should occur but it happens. Btw, parent state does not update when I enter new symbols into input. I tried to use spread operator as it is said here but it did not work.
Any ideas how to fix it would be welcome, thank you.
Probably a simple question and have so many people answered here but in this scenario, I cannot figure out why it doesn't work ...
In the parent I have
updateAccountNumber = value => {
console.log(value);
};
<Child updateAccountNumber={this.updateAccountNumber} />
In the child I have
<ListItem
button
key={relatedSub.office + relatedSub.account}
onClick={() =>
this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)
}
Even if I do parent like this, still no help..
<Child updateAccountNumber={() => this.updateAccountNumber()} />
if I have the below child item, then when I click on the menu that runs the child items, the component calls itself as many items as there are...
onClick={this.props.updateAccountNumber(
relatedSub.office + relatedSub.account
)}
It won't even run the below code, simple code, I can't see why it wouldn't kick of the handleClick event...
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("sda");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
Links = () => {
if (this.props.RelatedSubAccounts) {
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}
Please ask if any other related code is missing, and I can share.
I was able to get an example that works with the code you shared by using RelatedSubAccounts like this:
<RelatedSubAccounts RelatedSubAccounts={[{ office: 1, account: 2 }]} />
Code Sandbox Example
I see a few things that stand out as potentially confusing. I will point them out in code below with comments:
import React, { Component } from "react";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
const handleClick = () => {
debugger;
alert("RelatedSubAccounts clicked");
console.log("bbb");
};
export default class RelatedSubAccounts extends Component {
// Capitalization like this in react normally indicates a component
Links = () => {
/*
Having a prop that is the same name as the component and capitalized is confusing
In general, props aren't capitalized like the component unless you are passing a component as a prop
*/
if (this.props.RelatedSubAccounts) {
// Again, capitalization on RelatedSubArray hints that this is a component when it really isn't
let RelatedSubArray = this.props.RelatedSubAccounts;
let source = RelatedSubArray.map(relatedSub => (
<ListItem
button
onClick={handleClick}
key={relatedSub.office + relatedSub.account}
className={
relatedSub.office + relatedSub.account !== this.props.OfficeAccount
? ""
: "CurrentRelatedSub"
}
>
<ListItemText primary={relatedSub.office + relatedSub.account} />
</ListItem>
));
return (
<div id="RelatedSubLinks">
<List>{source}</List>
</div>
);
} else {
return "";
}
};
render() {
return this.Links();
}
}
This is going to be strangest solution but might give you a lesson or two.. I found the cause of the issue here.
So I have a menu like this
When you click on that arrow to open up the menu, it becomes active and when you click away onBlur kicks in and that menu goes away.. (menu created used react-select Creatable)
DropdownIndicator = props => {
return (
<div
onBlur={() => {
this.setState({ Focused: false, RelatedSub: false });
}}
so I had to update it to below:
onBlur={() => {
this.setState({ Focused: false });
setTimeout(() => {
this.setState({ RelatedSub: false });
}, 100);
}}
I have a todos list that I am working on. I want to be able to mark the task complete by clicking the checkbox or anywhere in the div of that todo. However, I get a warning when I only have the onClick event on the parent component
Here is the component code that works but gives me a warning:
render(){
const {todo, handleClick} = this.props;
const className = this.getClassName(todo.complete)
return (
<div
className={className}
onClick={handleClick}
>
<input
type="checkbox"
className="todo-checkbox"
checked={todo.complete}
/>
<span
className='todo-text'>
{todo.text}
</span>
</div>
)
}
}
and here is the warning:
index.js:1375 Warning: Failed prop type: You provided a checked prop to a form field without an onChange handler. This will render a read-only field. If the field should be mutable use defaultChecked. Otherwise, set either onChange or readOnly.
To fix this I used the suggested e.stopPropagation(); and added an onChange event to the child element. However, now only the parent div is working, so I can change successfully mark a todo anywhere in the div except for the checkbox. I think this is because they share the same method that it's not separating them as two different events.
stopBubbling = (e) => {
e.stopPropagation();
}
handleChange = (e, key) => {
this.stopBubbling(e)
this.setCompletebyId(e, key)
}
setCompletebyId = (e, key) => {
const { todos } = this.state;
const index = key - 1;
const complete = todos[index].complete;
todos[index].complete = !complete;
this.setState({
todos
})
}
Any help is appreciated!
Have you tried putting the onClick in your first example on the input itself and not the div?
From React's perspective, it assumes the input is "uncontrolled" and the user cannot interact with it. So the warning is providing options if that is the case. But in this scenario, you want it to be controlled. It was working because the click event on the input checkbox would bubble up to the div and still invoke the click handler.
class Checkbox extends React.Component {
render() {
const { todo, handleClick } = this.props;
return (
<label>
<input
type="checkbox"
className="todo-checkbox"
onClick={handleClick}
checked={todo.complete}
/>
{todo.text}
</label>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todo: { complete: false, text: "TODO" }
};
}
handleClick = () => {
this.setState({ todo: { ...this.state.todo, complete: !this.state.todo.complete } });
};
render() {
const { todo } = this.state;
return <Checkbox todo={todo} handleClick={this.handleClick} />;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I think putting
onChange = {handleClick}
inside the input tag might help as a checkbox expects an onChange function.
In my React component I have a button meant to send some data over AJAX when clicked. I need to happen only the first time, i.e. to disable the button after its first use.
How I'm trying to do this:
var UploadArea = React.createClass({
getInitialState() {
return {
showUploadButton: true
};
},
disableUploadButton(callback) {
this.setState({ showUploadButton: false }, callback);
},
// This was simpler before I started trying everything I could think of
onClickUploadFile() {
if (!this.state.showUploadButton) {
return;
}
this.disableUploadButton(function() {
$.ajax({
[...]
});
});
},
render() {
var uploadButton;
if (this.state.showUploadButton) {
uploadButton = (
<button onClick={this.onClickUploadFile}>Send</button>
);
}
return (
<div>
{uploadButton}
</div>
);
}
});
What I think happens is the state variable showUploadButton not being updated right away, which the React docs says is expected.
How could I enforce the button to get disabled or go away altogether the instant it's being clicked?
The solution is to check the state immediately upon entry to the handler. React guarantees that setState inside interactive events (such as click) is flushed at browser event boundary. Ref: https://github.com/facebook/react/issues/11171#issuecomment-357945371
// In constructor
this.state = {
disabled : false
};
// Handler for on click
handleClick = (event) => {
if (this.state.disabled) {
return;
}
this.setState({disabled: true});
// Send
}
// In render
<button onClick={this.handleClick} disabled={this.state.disabled} ...>
{this.state.disabled ? 'Sending...' : 'Send'}
<button>
What you could do is make the button disabled after is clicked and leave it in the page (not clickable element).
To achieve this you have to add a ref to the button element
<button ref="btn" onClick={this.onClickUploadFile}>Send</button>
and then on the onClickUploadFile function disable the button
this.refs.btn.setAttribute("disabled", "disabled");
You can then style the disabled button accordingly to give some feedback to the user with
.btn:disabled{ /* styles go here */}
If needed make sure to reenable it with
this.refs.btn.removeAttribute("disabled");
Update: the preferred way of handling refs in React is with a function and not a string.
<button
ref={btn => { this.btn = btn; }}
onClick={this.onClickUploadFile}
>Send</button>
this.btn.setAttribute("disabled", "disabled");
this.btn.removeAttribute("disabled");
Update: Using react hooks
import {useRef} from 'react';
let btnRef = useRef();
const onBtnClick = e => {
if(btnRef.current){
btnRef.current.setAttribute("disabled", "disabled");
}
}
<button ref={btnRef} onClick={onBtnClick}>Send</button>
here is a small example using the code you provided
https://jsfiddle.net/69z2wepo/30824/
Tested as working one: http://codepen.io/zvona/pen/KVbVPQ
class UploadArea extends React.Component {
constructor(props) {
super(props)
this.state = {
isButtonDisabled: false
}
}
uploadFile() {
// first set the isButtonDisabled to true
this.setState({
isButtonDisabled: true
});
// then do your thing
}
render() {
return (
<button
type='submit'
onClick={() => this.uploadFile()}
disabled={this.state.isButtonDisabled}>
Upload
</button>
)
}
}
ReactDOM.render(<UploadArea />, document.body);
You can try using React Hooks to set the Component State.
import React, { useState } from 'react';
const Button = () => {
const [double, setDouble] = useState(false);
return (
<button
disabled={double}
onClick={() => {
// doSomething();
setDouble(true);
}}
/>
);
};
export default Button;
Make sure you are using ^16.7.0-alpha.x version or later of react and react-dom.
Hope this helps you!
If you want, just prevent to submit.
How about using lodash.js debounce
Grouping a sudden burst of events (like keystrokes) into a single one.
https://lodash.com/docs/4.17.11#debounce
<Button accessible={true}
onPress={_.debounce(async () => {
await this.props._selectUserTickets(this.props._accountId)
}, 1000)}
></Button>
If you disable the button during onClick, you basically get this. A clean way of doing this would be:
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
export default function CalmButton(props) {
const [executing, setExecuting] = useState(false);
const {
disabled,
onClick,
...otherProps
} = props;
const onRealClick = async (event) => {
setExecuting(true);
try {
await onClick();
} finally {
setExecuting(false);
}
};
return (
<Button
onClick={onRealClick}
disabled={executing || disabled}
{...otherProps}
/>
)
}
See it in action here: https://codesandbox.io/s/extended-button-that-disabled-itself-during-onclick-execution-mg6z8
We basically extend the Button component with the extra behaviour of being disabled during onClick execution. Steps to do this:
Create local state to capture if we are executing
Extract properties we tamper with (disabled, onClick)
Extend onClick operation with setting the execution state
Render the button with our overridden onClick, and extended disabled
NOTE: You should ensure that the original onClick operation is async aka it is returning a Promise.
By using event.target , you can disabled the clicked button.
Use arrow function when you create and call the function onClick. Don't forget to pass the event in parameter.
See my codePen
Here is the code:
class Buttons extends React.Component{
constructor(props){
super(props)
this.buttons = ['A','B','C','D']
}
disableOnclick = (e) =>{
e.target.disabled = true
}
render(){
return(
<div>
{this.buttons.map((btn,index) => (
<button type='button'
key={index}
onClick={(e)=>this.disableOnclick(e)}
>{btn}</button>
))}
</div>
)}
}
ReactDOM.render(<Buttons />, document.body);
const once = (f, g) => {
let done = false;
return (...args) => {
if (!done) {
done = true;
f(...args);
} else {
g(...args);
}
};
};
const exampleMethod = () => console.log("exampleMethod executed for the first time");
const errorMethod = () => console.log("exampleMethod can be executed only once")
let onlyOnce = once(exampleMethod, errorMethod);
onlyOnce();
onlyOnce();
output
exampleMethod executed for the first time
exampleMethod can be executed only once
You can get the element reference in the onClick callback and setAttribute from there, eg:
<Button
onClick={(e) => {
e.target.setAttribute("disabled", true);
this.handler();
}}
>
Submit
</Button>
Keep it simple and inline:
<button type="submit"
onClick={event => event.currentTarget.disabled = true}>
save
</button>
But! This will also disable the button, when the form calidation failed! So you will not be able to re-submit.
In this case a setter is better.
This fix this set the disabled in the onSubmit of the form:
// state variable if the form is currently submitting
const [submitting, setSubmitting] = useState(false);
// ...
return (
<form onSubmit={e => {
setSubmitting(true); // create a method to modify the element
}}>
<SubmitButton showLoading={submitting}>save</SubmitButton>
</form>
);
And the button would look like this:
import {ReactComponent as IconCog} from '../../img/icon/cog.svg';
import {useEffect, useRef} from "react";
export const SubmitButton = ({children, showLoading}) => {
const submitButton = useRef();
useEffect(() => {
if (showLoading) {
submitButton.current.disabled = true;
} else {
submitButton.current.removeAttribute("disabled");
}
}, [showLoading]);
return (
<button type="submit"
ref={submitButton}>
<main>
<span>{children}</span>
</main>
</button>
);
};
Another approach could be like so:
<button onClick={this.handleClick} disabled={isLoading ? "disabled" :""}>Send</button>
My approach is if event on processing do not execute anything.
class UploadArea extends React.Component {
constructor(props) {
super(props)
this.state = {
onProcess:false
}
}
uploadFile() {
if (!this.state.onProcess){
this.setState({
onProcess: true
});
// then do your thing
this.setState({
onProcess: false;
});
}
}
render() {
return (
<button
type='submit'
onClick={() => this.uploadFile()}>
Upload
</button>
)
}
}
ReactDOM.render(<UploadArea />, document.body);
Try with this code:
class Form extends React.Component {
constructor() {
this.state = {
disabled: false,
};
}
handleClick() {
this.setState({
disabled: true,
});
if (this.state.disabled) {
return;
}
setTimeout(() => this.setState({ disabled: false }), 2000);
}
render() {
return (
<button type="submit" onClick={() => this.handleClick()} disabled={this.state.disabled}>
Submit
</button>
);
}
}
ReactDOM.render(<Form />, document.getElementById('root'));