Focus on newly-rendered text input after tab-fired blur event - javascript

I have a React (15.3.2) component with a text input.
(Everywhere I say "render" here it's actually render or unhide; I've tried both.)
When that input element is blurred, I render a new component with a text input.
I want give the new text input focus.
I've tried componentDidMount, componentWillUpdate, and componentDidUpdate; I've tried named and function refs; I've tried react-dom.
The focusing itself works, e.g., once it's been rendered, if I click in the initial input, focus goes to the new input (this is a bug, but compared to focusing, trivial).
The first input has an onBlur that sets the state used to tell the second input to render or not.
In that blur handler I stop the event as best as I can.
When I tab out of the first element I'm already "past" the newly-rendered element, e.g., the browser tab bar in my current bare design–I guess the new element hasn't been rendered yet?
class SecondInput extends Component {
componentDidUpdate = (prevProps, prevState) => {
if (!this.props.hidden) this._input.focus()
}
render = () =>
<input type="text" hidden={this.props.hidden} ref={(c) => this._input = c}
}
class NewItem extends Component {
state = { itemEntered: false }
itemBlurred = (e) => {
e.preventDefault()
e.stopPropagation()
this.setState({ itemEntered: true })
}
render = () =>
<div>
Item: <input type="text" onBlur={this.itemBlurred} />
<SecondInput hidden={!this.state.itemEntered} />
</div>
}
Any ideas or hints? I have to believe it's something obvious, because surely this happens all the time.
I'm also open to any other form of component hierarchy, e.g., if I need to have a container that wraps all this stuff up somehow that's fine.
React 15.3.2

The problem you are seeing appears to be because there are no more focusable elements on the page when you press tab, so the focus goes to the address bar. For some reason when the focus is on the address bar, just calling this._input.focus() does not grab focus as you would expect.
In order to combat this problem, I have added an empty div, and set the tabIndex property based on whether or not the second input is shown.
Just to make things easier for myself, I made the input focus on mount instead of using the hidden property. This may or may not work in your case, but it seemed to be cleaner since it would keep the input from calling focus on every keypress if it were to be a controlled input.
let Component = React.Component;
class SecondInput extends Component {
componentDidMount(){
this.textInput.focus()
}
render(){
return (
<input type="text" ref={(input) => this.textInput = input} />
)
}
}
class NewItem extends Component {
state = { itemEntered: false }
itemBlurred = (e) => {
//e.preventDefault()
//e.stopPropagation()
this.setState({ itemEntered: true })
}
render = () =>
<div>
Item: <input type="text" onBlur={this.itemBlurred} />
{
this.state.itemEntered ? [
<SecondInput/>
] : []
}
<div tabIndex={this.state.itemEntered ? null : 0}/>
</div>
}
ReactDOM.render(<NewItem />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.0/react-dom.min.js"></script>

Related

How to Focus on nested Element by passing reference to the Parent Component in React?

I have a parent Component which sends a list of data to a List component which in turn sends it to a Label Component to display each data as a label.
I want to be able to focus on the label element when i click on it so that the appropriate style is applied ..
Below is the gist :-
class ContainerComp extends React.Component {
constructor(props) {
super(props);
this.state = {
group: [1, 2, 3]
};
clickHandler = (name, ref) = > {
// I am able to get the DIV as a html element here but calling .focus() on it dosent change the style where as when i explictly add focus using chrome debugger for the element it works.
ref.focus() // not working
}
render() {
return ( <
ListComp group = {
group
}
onClick = {
clickHandler
} >
)
}
}
function ListComp(props) {
const data = props.group.map(... < label onClick = {} > )
return ( <
Label.. >
)
}
function Label(props) {
let ref = createref();
// on focus style for the component is defined in this component
// i am making use of css modules
return ( <
div ref = {
ref
}
onClick = (name, ref) >
)
}
<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>
How can we achieve such a functionality without having to pass a selected prop to the label component ? By default i would select the first element and keep the focus .. or we can make it configurable.
Usually for this I would use Redux and fire off an action which therefore sets the property of the component that needs change, and make a listener that will listen for that specific prop and change style accordingly.
In this situation, id just pass down the event handler to the child component (remember to not call it when you pass it down, so do:
{() => {eventHandler()}}
and then in the child component do:
onClick={this.props.eventHandler(e)}
You will use the event to identify which element triggered it and then apply the class/style/prop to it.
There was some problem with the Ref , I am not quite sure why but i changed it to use the useRef() hook.
Label Component
const elementRef = useRef(null);
return (
<div className={[externalStyle, styles.container].join(' ')} onClick={() => onClickEvent(itemName, elementRef)} ref = {elementRef} tabIndex={1}> // added tabIndex and also changed to useRef
Container Component
clickHandler = (name: string, ref) => {
ref.current.focus(); // volla it worked
}
I tried using the old form of Ref and also useRef() without null previously (el) => (const = el).
It works if some one has some explanation where i went wrong i will be happy to listen as i am able to wrap my head around. may be a nights sleep helped fix it :P

Implement autosave on input tag in React.js

I'm trying to implement the Google Tasks Add Task feature (See pic for reference). Basically, when you click on Add Task button, it opens an input field which autosaves as you type. I'm trying to implement this in React.js.
class App extends Component {
state = {
showInput: false,
addTaskInput: ''
}
showAddTask = (e) => {
this.setState({showInput: true})
}
saveInput = (e) => {
this.setState({addTaskInput: e.target.value})
}
render() {
const {showInput, addTaskInput} = this.state;
return (
<div className="app">
<Button
message="Add Task"
bsStyle="primary"
onClick={this.showAddTask}
/>
{ showInput && <input
type="text"
placeholder="Add Task here..."
value={addTaskInput}
onChange={this.saveInput}
/> }
<TodoList addItem={}/>
</div>
);
}
}
Here is my code. App is my main component. TodoList is the component which has the whole list of Todos. What I am going to do is:
When I type something in the input, it fires onChange and sets the state. Inside onChange I would also change the addItem prop which would re-render TaskList. This doesn't seem very optimal because of unnecessary re-renders. I'm thinking of changing addItem prop when focus on input is removed.
How do I go about doing the latter? Alternative approaches are welcome.
I would say it depends on how much your app is doing; if it's just a simple app saving onChange is not bad, but if a lot else is going on, yeah you should optimise.
onBlur
mix keystroke and debounce (my favorite)
when input is more than a set length

Set dynamic state name in React.js

I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen

How to make the input not to lose focus when using state and Function components in ReactJS?

I use a Function component to render a component.
But the input control loses focus whenever the state is set.
How to change the below code so that the input doesnt' lose focus? I'd need the component to be a Function component.
There are 10 inputs on my form so depending on which text box has changed, it should place the focus on the changed text box after being rendered.
in the changeHandler(event), I know the event which contains the target property which refers to the changed text box so somehow I'd need to set the focus to the target input element after the state being rendered.
maybe I should define a class level variable and set the target to that.
import React, {Component} from 'react';
export default class PlaybackPassword extends Component{
constructor(props) {
super(props);
this.state = {
setting: {}
}
this.focusElement = null;
this.changeHandler= this.changeHandler.bind(this);
}
componentDidUpdate()
{
if (this.focusElement != null)
this.focusElement.focus();
}
changeHandler(event)
{
this.focusElement = event.target;
var setting = this.state.setting;
setting.SelectedValue = event.target.value;
this.setState({setting : setting});
}
render() {
var parent = this;
function Password(props)
{
return (<div className="password">
<input type="password"
value={props.selectedValue} onChange={parent.changeHandler} />
</div>);
}
return (
<div className="playbackPassword">
<Password selectedValue={this.state.setting.SelectedValue} />
</div>
);
}
}
In the example code, you are defining (i.e., creating) a new function Password on every render. This means that your <Password> component will be a brand new component every time.
The key is to define your function once, outside of the render function, so that React can track that the <Password> component is in fact the same component between renders.
Example: https://codesandbox.io/s/nn2nwq2lzp
Note: you won't be able to use parent but will need to pass a prop instead.

Update value of input type time (rerender) and focus on element again with React

In the spec for my app it says (developerified translation): When tabbing to a time element, it should update with the current time before you can change it.
So I have:
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} />
Also relevant
changes(evt) {
let ch = {};
ch[evt.target.name] = evt.target.value;
this.model.set(ch);
},
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
}
},
The component will update when the model changes. This works well, except that the input loses focus when tabbing to it (because it gets rerendered).
Please note, if I would use an input type="text" this works out of the box. However I MUST use type="time".
So far I have tried a number of tricks trying to focus back on the element after the re-render but nothing seems to work.
I'm on react 0.14.6
Please help.
For this to work, you would need to:
Add a focusedElement parameter to the components state
In getInitialState(): set this parameter to null
In handleTimeFocus(): set focusElement to 'timeElem` or similar
Add a componentDidUpdate() lifecycle method, where you check if state has focusedElement set, and if so, focus the element - by applying a standard javascript focus() command.
That way, whenever your component updates (this is not needed in initial render), react checks if the element needs focus (by checking state), and if so, gives the element focus.
A solution for savages, but I would rather not
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
this.forceUpdate(function(){
event.target.select();
});
}
},
try using autoFocus attrribute.
follow the first 3 steps mention by wintvelt.
then in render function check if the element was focused, based on that set the autoFocus attribute to true or false.
example:
render(){
var isTimeFocused = this.state.focusedElement === 'timeElem' ? true : false;
return(
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} autoFocus={isTimeFocused} />
);
}

Categories