I have two (or more) HTML elements and I want to execute a callback whenever any of them loses focus except when the focus is moved between them. In other words I want to treat those elements like a single one from the focus point of view and execute a group-onBlur callback.
Using React I tried to keep track of the the focus of each element on a state, but when moving the focus from element a to element b, onBlur on a always happens before onFocus on b. Also since the state update may be async I'm not sure about the consistency of the state.
You can store refs to each of the inputs that you want to "share" focus and then check if either is focused in your onBlur function before taking any actions. Note, however that the focused element is the body if you check immediately when the blur happens. To get around this you can wrap your onBlur logic in a setTimeout call with a delay of 0ms.
Here's an example:
function MyComponent() {
const aRef = React.useRef(null);
const bRef = React.useRef(null);
function onBlurGroup() {
window.setTimeout(() => {
if (document.activeElement === aRef.current || document.activeElement === bRef.current) {
console.log("a or b is focused");
return;
}
console.log("focus lost from group");
// do blur handling
}, 0)
}
return (
<div>
<input ref={aRef} name="a" type="text" onBlur={onBlurGroup}/>
<input ref={bRef} name="b" type="text" onBlur={onBlurGroup}/>
<input name="other" type="text"/>
</div>
);
}
And a functioning sample (using a class-based component since Stack doesn't support hooks yet):
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.aRef = React.createRef();
this.bRef = React.createRef();
}
onBlurGroup = () => {
window.setTimeout(() => {
if (document.activeElement === this.aRef.current || document.activeElement === this.bRef.current) {
console.log("a or b is focused");
return;
}
console.log("focus lost from group");
// do stuff
}, 0)
}
render() {
return (
<div>
<input ref={this.aRef} name="a" placeholder="A" type="text" onBlur={this.onBlurGroup}/>
<input ref={this.bRef} name="b" placeholder="B" type="text" onBlur={this.onBlurGroup}/>
<input name="other" placeholder="Other" type="text"/>
</div>
);
}
}
ReactDOM.render(<MyComponent/>, document.querySelector("#root"));
<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"/>
Typically you pull it off with a timeout and wait for a few milliseconds before firing text step. Quick and dirty react code.
class Test extends React.Component {
constructor(props) {
super(props)
this.state = {
blurTimer: null
}
this.handleBlur = this.handleBlur.bind(this);
this.handleFocus = this.handleFocus.bind(this);
}
clearTimer() {
if (this.state.blurTimer) {
window.clearTimeout(this.state.blurTimer);
this.setState({ blurTimer: null });
}
}
processIt() {
console.log('Process it!!!!');
}
handleBlur() {
this.clearTimer();
const blurTimer = window.setTimeout(() => this.processIt(), 100);
this.setState({ blurTimer });
}
handleFocus() {
this.clearTimer();
}
render() {
return (
<div>
<h2>foo</h2>
<input type="text" onBlur={this.handleBlur} onFocus={this.handleFocus} />
<input type="text" onBlur={this.handleBlur} onFocus={this.handleFocus} />
</div>
)
}
}
ReactDOM.render(<Test />, document.querySelector("#app"))
<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="app"></div>
Related
I'm trying to make a piece of code a little more efficient. The concept is that when you focus the textarea, you are shown a paragraph tag that tells you the characters remaining. Currently i have:
import React, { Component } from "react";
class Idea extends Component {
state = {
showCharactersRemaining: false
};
calculateRemaining() {
return 15 - this.props.body.length;
}
onTextAreaFocus = () => {
this.setState(state => {
return { showCharactersRemaining: true };
});
};
onBlur = () => {
this.setState(state => {
return { showCharactersRemaining: false };
});
};
render() {
const { title, body } = this.props;
const { showCharactersRemaining } = this.state;
return (
<div className="idea">
<input type="text" name="title" value={title} />
<textarea
rows="3"
name="body"
maxlength="15"
value={body}
onFocus={this.onTextAreaFocus}
onBlur={this.onBlur}
/>
{showCharactersRemaining && (
<p>{this.calculateRemaining()} characters remaining.</p>
)}
</div>
);
}
}
export default Idea;
Which works but also relies on having two methods attached to said textarea to make it work. Is there a smarter way in react of doing this?
CSS can handle it for you, removing the necessity for state and either event handler. Run the snippet to see it work (I removed the logic for counting characters to keep this example simple)
.charcount {
display: none;
}
textarea:focus+.charcount {
display: block;
}
<div className="idea">
<textarea rows="3" name="body" maxlength="15" value="foo"></textarea>
<p class="charcount">XX characters remaining.</p>
</div>
I'm doing a React coding challenge that requires a value to be updated onKeyUp. I initially set it to update onChange but the tests require onKeyUp so I tried to change it to that, but my fields are no longer updating and I can't type anything into the textarea.
class MarkdownApp extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp(event) {
this.setState({ value: event.target.value })
}
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
<br />
</label>
<label>
Your markup will be previewed here:
<p id='preview'>{marked(this.state.value)}</p>
</label>
</form>
);
}
}
ReactDOM.render(
<MarkdownApp />,
document.getElementById('root')
);
Like I said, this worked fine when it was onChange and my function was handleChange, but since I switched it I can't type anything.
I would just remove the value attribute from the textarea. Because if you put the value attribute to it then the user won't be able to change it interactively. The value will always stay fixed(unless you explicitly change the value in your code). You don't need to control that with React--the DOM will hold onto the value for you.
The only change I've made below is to remove value={this.state.value} from the textarea element:
import React from 'react';
import ReactDOM from 'react-dom';
class MarkdownApp extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp(event) {
this.setState({ value: event.target.value })
}
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
<br />
</label>
<label>
Your markup will be previewed here:
<p id='preview'>{this.state.value}</p>
</label>
</form>
);
}
}
ReactDOM.render(
<MarkdownApp />,
document.getElementById('root')
);
Since the event happens before the actual value of the textbox is changed, the result of event.target.value is an empty string. Setting the state with the empty string, clears the textbox.
You need to get the pressed key value from the event, and add it to the existing state.value.
Note: I've removed marked from the demo
class MarkdownApp extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp(event) {
const keyValue = event.key;
this.setState(({ value }) => ({
value: value + keyValue
}))
}
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
<br />
</label>
<label>
Your markup will be previewed here:
<p id='preview'>{this.state.value}</p>
</label>
</form>
);
}
}
ReactDOM.render(
<MarkdownApp />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
You could make the textarea uncontrolled by not giving it the value and simply storing the value in state from a ref instead.
Example (CodeSandbox)
class MarkdownApp extends React.Component {
ref = null;
state = {
value: ""
};
handleKeyUp = event => {
this.setState({ value: this.ref.value });
};
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea
onKeyUp={this.handleKeyUp}
ref={ref => (this.ref = ref)}
id="editor"
/>
<br />
</label>
<label>
Your markup will be previewed here:
<p id="preview">{marked(this.state.value)}</p>
</label>
</form>
);
}
}
The issue is you have a two way binding with the state = to the value in your textbox. OnChange would update your state after a change is made and the events are done firing. Onkeyup returns the value onkeyup and since you mapped that to your state it will stay as nothing. Remove the value prop and it should work.
I have small class in react, i want to display the result on the screen after i click on the button, but before the display happens, the page reload.
how do i do it?
what am I missing?
import React, {Component} from 'react';
class InputFieldWithButton extends Component{
constructor(props){
super();
this.state = {
message: ''
};
}
handleChange(e){
this.setState({
message: e.target.value
});
}
doSomething(e){
return(
<h1>{this.state.message}</h1>
)
}
render(){
return (
<div>
<form >
<input type="text" placeholder="enter some text!" value=
{this.state.message}
onChange={this.handleChange.bind(this)}/>
<button onClick={this.doSomething.bind(this)}>Click me</button>
</form>
</div>
)
}
}
export default InputFieldWithButton;
Your button is inside a form and triggering a submit.
You can use the preventDefault() method to stop it from doing so:
doSomething(e) {
e.preventDefault();
return (
<h1>{this.state.message}</h1>
)
}
By the way, your return statement of this click handler makes no sense at the moment.
Edit
As a followup to your comment:
Can you explain me what is my mistake in the return?
Not really a mistake, but it is useless in this context as your are not doing anything with the returned object.
Where and how do you expect to use the <h1>{this.state.message}</h1> that you are returning?
If you intend to show / hide the input message in your screen you could do it with conditional rendering.
Just store a bool like showMessage in your state and render the message only if it's set to true.
Here is a small example:
class InputFieldWithButton extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '',
showMessage: false
};
}
handleChange = (e) => {
this.setState({
message: e.target.value
});
}
toggleMessage = (e) => {
e.preventDefault();
this.setState({ showMessage: !this.state.showMessage })
}
render() {
const { showMessage, message } = this.state;
return (
<div>
<form >
<input
type="text"
placeholder="enter some text!"
value={message}
onChange={this.handleChange}
/>
<button onClick={this.toggleMessage}>Toggle Show Message</button>
{showMessage && <div>{message}</div>}
</form>
</div>
)
}
}
ReactDOM.render(<InputFieldWithButton />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
By the way, it is considered as bad practice to bind the functions inside the render method, because you are creating a new instance of a function on each render call. instead do it inside the constructor which will run only once:
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
Or you can use arrow functions which will reference this in a lexical context:
handleChange = (e) => {
this.setState({
message: e.target.value
});
}
This is what i've used in my example.
you're not specifying the buttons'type
<button type="button">
Set the type attribute on the button to be button. The default is submit since it is wrapped in a form. So your new button html should look like this:
<button type="button" onClick={this.doSomething.bind(this)}>Click me</button>
I'm rendering an Input of type='number'.
The Input has the value of this.state.value.
The Input and all the UI Components are generated via Semantic-UI, but I think that's not of a significant importance info.
I also have a custom arrow menu for this input instead of the original one. [input of type number has two arrows to decrease/increase the value]
Render()
render() {
// Custom Menu
const arrowsMenu = (
<Menu compact size='tiny'>
<Menu.Item as='a' onClick={ this.decreaseNumber.bind(this) }>
<Icon name='chevron left' size='small' />
</Menu.Item>
<Menu.Item as='a' onClick={ this.increaseNumber.bind(this) }>
<Icon name='chevron right' size='small' />
</Menu.Item>
</Menu>
);
return (
<Input value={ this.state.value } type="number" label={ arrowsMenu } placeholder="Raplece ma" onChange={ this.onChange.bind(this) } />
);
}
The Custom Menu uses these two functions:
decreaseNumber(e) {
this.setState({
value: this.state.value - 1
});
}
increaseNumber(e) {
this.setState({
value: this.state.value + 1
});
}
onChange
You can place anything.
onChange(e) {
console.log('====================================');
console.log('Hello pals');
console.log('====================================');
}
The problem is
That whenever I push an Arrow from the Menu, the onChange() event of the Input is not triggered. But the value of the input is changed.
(Of course, because the this.state.value variable is changed in the state)
If I do the same with the original arrows, of course, the value is changed as it should.
Why is that and how can I fix it?
onChange is only called if the user goes into the Input component and interacts with it to change the value (e.g. if they type in a new value). onChange is not called if you change the value programmatically through some other avenue (in your example changing it via the custom menu).
This is working as intended design.
If you want to trigger onChange, then call it from your increaseNumber and decreaseNumber methods.
You can call onChange with any code you want, but if you want to reflect the new value you need to set the state according to the new input value from the event.
As for decreaseNumber and increaseNumber you need to change the state as well but here you are doing calculation so you need to make sure it's a number (or convert it to a number) because you are getting a string back from the event.
Working example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
onChange = e => this.setState({value: e.target.value});
increaseNumber = () => this.setState({value: Number(this.state.value) + 1});
decreaseNumber = () => this.setState({ value: Number(this.state.value) - 1 });
render() {
const { value } = this.state;
return (
<div>
<button onClick={this.decreaseNumber}>-</button>
<input type="number" value={value} onChange={this.onChange}/>
<button onClick={this.increaseNumber}>+</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Edit
For triggering the onChange handler just call this.onChange but note that you can't pass the event like the native event handler does but you can pass a simple object that mimic the normal event object with a target.value.
Another option is to try triggering it via a ref but keep in mind it can cause an infinite loop in some cases.
Edited Example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
onChange = (e) => {
this.setState({ value: e.target.value });
console.log('change', e.target.value);
}
increaseNumber = () => {
const { value } = this.state;
const nextValue = Number(value) + 1;
const changeEvent = {
target: {
value: nextValue
}
};
this.onChange(changeEvent);
}
decreaseNumber = () => {
const { value } = this.state;
const nextValue = Number(value) - 1;
const changeEvent = {
target: {
value: nextValue
}
};
this.onChange(changeEvent);
}
render() {
const { value } = this.state;
return (
<div>
<button onClick={this.decreaseNumber}>-</button>
<input type="number" value={value} onChange={this.onChange} />
<button onClick={this.increaseNumber}>+</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Edit #2
As a followup to your comment:
list's value is its content/children. If some of the children change,
then list changes with them as well
Well, this has an easy solution, you can use the ref (like i mentioned in the first section of my answer) and dispatch an event with bubbles:true so it will bubble all the way up to the parents.
Using your new example code:
class App extends React.Component {
liOnChange(e) {
console.log('listed item/items changed');
}
inputOnChange(e) {
console.log('input changed');
}
handleClick(e){
var event = new Event("input", { bubbles: true });
this.myInput.dispatchEvent(event);
}
render() {
return(
<div>
<ul>
<li onChange={this.liOnChange.bind(this)}>
<input ref={ref => this.myInput = ref} type='text'onChange={this.inputOnChange.bind(this)}/>
<button onClick={this.handleClick.bind(this)}>+</button>
</li>
</ul>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I in general don't like using refs but sometimes you need them.
I'm teaching myself react with a super simple app that asks the user to type a word presented in the UI. If user enters it correctly, the app shows another word, and so on.
I've got it almost working, except for one thing: after a word is entered correctly, I need to clear the input element. I've seen several answers here about how an input element can clear itself, but I need to clear it from the component that contains it, because that's where the input is checked...
// the app
class AppComponent extends React.Component {
constructor() {
super();
this.state = {
words: ['alpha', 'bravo', 'charlie'],
index: 0
};
}
renderWordsource() {
const word = this.state.words[this.state.index];
return <WordsourceComponent value={ word } />;
}
renderWordinput() {
return <WordinputComponent id={1} onChange={ this.onChange.bind(this) }/>;
}
onChange(id, value) {
const word = this.state.words[this.state.index];
if (word == value) {
alert('yes');
var nextIndex = (this.state.index == this.state.words.count-1)? 0 : this.state.index+1;
this.setState({ words:this.state.words, index:nextIndex });
}
}
render() {
return (
<div className="index">
<div>{this.renderWordsource()}</div>
<div>{this.renderWordinput()}</div>
</div>
);
}
}
// the input component
class WordinputComponent extends React.Component {
constructor(props) {
this.state = { text:''}
}
handleChange(event) {
var text = event.target.value;
this.props.onChange(this.props.id, text);
}
render() {
return (
<div className="wordinput-component">
<input type="text" onChange={this.handleChange.bind(this)} />
</div>
);
}
}
See where it says alert('yes')? That's where I think I should clear the value, but that doesn't make any sense because it's a parameter, not really the state of the component. Should I have the component pass itself to the change function? Maybe then I could alter it's state, but that sounds like a bad idea design-wise.
The 2 common ways of doing this is controlling the value through state in the parent or using a ref to clear the value. Added examples of both
The first one is using a ref and putting a function in the child component to clear
The second one is using state of the parent component and a controlled input field to clear it
class ParentComponent1 extends React.Component {
state = {
input2Value: ''
}
clearInput1() {
this.input1.clear();
}
clearInput2() {
this.setState({
input2Value: ''
});
}
handleInput2Change(evt) {
this.setState({
input2Value: evt.target.value
});
}
render() {
return (
<div>
<ChildComponent1 ref={input1 => this.input1 = input1}/>
<button onClick={this.clearInput1.bind(this)}>Clear</button>
<ChildComponent2 value={this.state.input2Value} onChange={this.handleInput2Change.bind(this)}/>
<button onClick={this.clearInput2.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent1 extends React.Component {
clear() {
this.input.value = '';
}
render() {
return (
<input ref={input => this.input = input} />
);
}
}
class ChildComponent2 extends React.Component {
render() {
return (
<input value={this.props.value} onChange={this.props.onChange} />
);
}
}
ReactDOM.render(<ParentComponent1 />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I had a similar issue: I wanted to clear a form which contained multiple fields.
While the two solutions by #noveyak are working fine, I want to share a different idea, which gives me the ability to partition the responsibility between parent and child: parent knows when to clear the form, and the items know how to react to that, without using refs.
The idea is to use a revision counter which gets incremented each time Clear is pressed and to react to changes of this counter in children.
In the example below there are three quite simple children reacting to the Clear button.
class ParentComponent extends React.Component {
state = {revision: 0}
clearInput = () => {
this.setState((prev) => ({revision: prev.revision+1}))
}
render() {
return (
<div>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<ChildComponent revision={this.state.revision}/>
<button onClick={this.clearInput.bind(this)}>Clear</button>
</div>
);
}
}
class ChildComponent extends React.Component {
state = {value: ''}
componentWillReceiveProps(nextProps){
if(this.props.revision != nextProps.revision){
this.setState({value : ''});
}
}
saveValue = (event) => {
this.setState({value: event.target.value})
}
render() {
return (
<input value={this.state.value} onChange={this.saveValue} />
);
}
}
ReactDOM.render(<ParentComponent />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
EDIT:
I've just stumbled upon this beautifully simple solution with key which is somewhat similar in spirit (you can pass parents's revision as child's key)
Very very very simple solution to clear form is add unique key in div under which you want to render form from your child component key={new Date().getTime()}:
render(){
return(
<div className="form_first_step fields_black" key={new Date().getTime()}>
<Form
className="first_step">
// form fields coming from child component
<AddressInfo />
</div>
</Form>
</div>
)
}