autoFocus on input when opening modal does not work - React Bootstrap - javascript

I have a modal that has 3 components. Each component represents a stage, so for example the first component is inputing the user's first name when the user click next, and it will go to the next component, which is inputing address and then user clicks next and it will take the user to the last stage, inputing a nickname. On every input element from the components, it will have an autoFocus. So far in the last two components, the inputs have an autoFocus except for the first component.
Not sure why the first doesn't have it, when I initialise the modal I see the input doesn't have the autoFocus, then I hit next then the second component input has it and I go back to the first component - hit the back button - then I see the input from the first component has an autoFocus. Very strange, I've been trying so many way to resolve this, from setting autoFocus={false} to the modal, creating a ref for the first input but still not working.
It looks like it's an issue with the modal, where it's initialized the focus is somewhere but not on the input. Has anyone encountered this issue?
Example (pseudo) code
//Component Hosting the modal
render(){
if(this.state.modal_stage === 1){
<FirstName />
}else if(this.state.modal_stage === 2){
<LasgtName />
}else if(this.state.modal_stage === 3){
<NickName />
}
return(
<Modal
dialogClassName="locationModal customModal"
show={this.state.modalShow} onHide={this.hideModal}
autoFocus="false">
<Modal.Header>
<div className="closeModal" onClick={this.hideModal}></div>
</Modal.Header>
<Modal.Body>
{ current_stage}
</Modal.Body>
<Modal.Footer>
{backButton}
{nextButton}
</Modal.Footer>
</Modal>
);
}
//<FirstName /> component
constructor(props){
super(props)
this.inputRef = React.createRef();
}
componentDidMount(){
this.inputRef.current.focus();
}
....
render(){
return(
<div>
<input type="text" placeholder="firstName" ref={this.inputRef}/>
</div>
);
}
Your help will be appreciated!!
Note:
I tried to set this autoFocus="false" to autoFocus={false} in the modal but the issue still remains.

Here is a similar solution as the one from #dev_junwen using a functional component and hooks. Also no need for a timeout.
import React, { useEffect, useRef } from 'react';
const FirstInput = ({ item, save }) => {
const innerRef = useRef();
useEffect(() => innerRef.current && innerRef.current.focus());
return <input ref={innerRef} />;
};

Found out a solution that will fix your problem. You can add a little timeout for your component to focus on your input. This should fix your issue.
class FirstInput extends React.Component {
constructor(props) {
super(props);
this.innerRef = React.createRef();
}
componentDidMount() {
// Add a timeout here
setTimeout(() => {
this.innerRef.current.focus();
}, 1)
}
render() {
return <input ref={this.innerRef} />;
}
}
This is a working example: https://codesandbox.io/s/react-bootstrap-autofocus-efkve?fontsize=14

Apparently there is a bug with react-bootstrap that doesn't allow autoFocus on a Modal component. The workaround while this bug is fixed is disabling animations on the Modal component.
<Modal animation={false}>
<Form.Control autoFocus />
</Modal>

Just add autoFocus = {false} on modal and autoFocus = {true} on input.
<Modal autoFocus={false}>
<Form>
<ModalHeader>Type Your Input</ModalHeader>
<ModalBody>
<Input autoFocus={true} />
<Button>Submit</Button>
</ModalBody>
</Form>
</Modal>
Ref: https://simplernerd.com/js-reactstrap-modal-autofocus/

You can use the onShow callback (that will be called after the animation is over) to set the focus to the inner field.
import React, {useRef} from 'react';
const MyForm = () => {
const innerRef = useRef();
return (
<Modal onShow={() => {innerRef.current.focus()}}>
<Form.Control ref={innerRef} />
</Modal>
)
}

This is supplement to ALoR answer:
bootstrap docs suggest using onEntered callback which is fired after the Modal finishes transitioning in. It worked for me well.
Also suggested here "autoFocus = {false} on modal" is discouraged as it messes up with screen readers

It looks like you are using a string and not a bool when setting autoFocus="false" - should be autoFocus={false}. This is why it works the second time as you are setting the value correctly via the ref: this.inputRef.current.focus();

Related

Changing state of one component from another component in another file

I am new to React and web dev in general.
I have created a Component containing list of calculator like buttons which is stored in Buttons.js.
There is another component called as Submit stored in Submit.js. Submit component is basically a textbox in which we type a mathematical expression which I want to process later.
Both of these components are then called in another component call Leftwindow.js.
So my question is,
How can I make clicking in Buttons component affect the textbox in Submit component. I know it could be done easily had the buttons and input box been the part of a single component.
Basically if I press the '1' button I want it to be added to the input box.
A snapshot of how it looks -
Overview
Code for Buttons.js -
class Buttons extends Component {
constructor(props){
super(props);
this.state = {
//buttonrows//
};
}
render(){
const row1elems = this.state.row1.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row2elems = this.state.row2.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row3elems = this.state.row3.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
const row4elems = this.state.row4.map((button) => {
return (
<Button color={colours[button.type]} className="buttonsize">{button.label}</Button>
);
});
return (
<div className="center">
<ButtonGroup>
{row1elems}
</ButtonGroup>
<ButtonGroup>
{row2elems}
</ButtonGroup>
<ButtonGroup>
{row3elems}
</ButtonGroup>
<ButtonGroup>
{row4elems}
</ButtonGroup>
</div>
);
}
}
export default Buttons;
Code for Submit.js -
class Submit extends Component{
constructor(props){
super(props);
this.state = {
fx: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event){
const target = event.target;
const val = target.val;
const name = target.name;
this.setState({
[name]: val
})
}
handleSubmit(event){
}
render(){
return(
<div>
<Form onSubmit={this.handleSubmit}>
<FormGroup row>
<Col md={12}>
<Input type="text" id="fx" name="fx" placeholder="Type Here" value = {this.state.fx} onChange={this.handleInputChange} />
</Col>
</FormGroup>
</Form>
<Button type="submit" color="primary">Differentiate</Button>
</div>
)
}
}
export default Submit;
Code for LeftWindow.js --
import React, { Component } from 'react';
import Buttons from './Buttons';
import './Custom.css';
import Submit from './Submit';
class LeftWindow extends Component{
constructor(props){
super(props);
}
render(){
return(
<div className="col-3 bg-dark fullheight">
<Buttons/>
<h3 className="center">Enter the function to be differentiated</h3>
<Submit/>
</div>
);
}
}
export default LeftWindow;
This is how your common ancestor file will look like -
Added state having input.
Added a callback "handleInputChange" which updates this input state.
Passed this callback to Both the components.
In your Submit.js file you will need to change your input tag with this
<Input
type="text"
value={this.props.input}
onChange={e => {
this.props.handleInputChange(e.target.value);
}}
/>
Also in your Buttons.js file call this.props.handleInputChange on Button click.
<Button
onClick={() => {
this.props.handleInputChange(button.label)
}}
color={colours[button.type]}
className="buttonsize"
>
{button.label}
</Button>
That's it.
Hope I could help!
In React you work with tree of components where data can travel from top to bottom. It is possible to notify parent component about changed data via passed callbacks. If you have two components that have common ancestor, you can share data between them through this common ancestor.
Let's say you have component Parent which renders your Buttons and Submit components. If you store your data (or state) in Parent and pass this data as props with callbacks, your components then can notify Parent about things happened and parent can change it's state and pass new state as props to children.
There is a "state management" solutions when your data lives detached of your components and injected on one by one basis. In such you won't need parent to store the data, but if we talk about pure react - to share data between branches in react tree, this branches should have common ancestor somewhere in the tree.

Loose focus on react input box after one character input

When I enter a character in the input box
The state updates with the new character
Then I loose focus on the input box
so I can only modify the box 1 keypress at at time
The input box is nested in 4 other components which includes 1 higher Order component (see below)
Page component
header(Modify)
InputForm
When I move the form code to the Page component it works.
How I keep the components separate (and reusable) and have the functionlity I need?
The form code is below
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
The full code for the entiure component is here
mport React, { Fragment } from 'react';
const InputForm = (props) => {
//prepare props
console.log('currentObject', props.currentObject);
const { editorState, stateModifier, userFunctions } = props.editorEssentials;
// const urlFormVisible = props.urlFormVisible;
//Styles
const componentStyle = 'container-flex-column';
// console.log('MOdify: currentState', editorState);
// console.log('MOdify: targetObject', currentObject);
// console.log('MOdify: stateModifier', stateModifier);
console.log('currentObject.name', props.currentObject.name);
return (
<Fragment>
<form
className={componentStyle}
// onSubmit={(event) =>
// userFunctions.submitItem(editorState, currentObject, stateModifier, event)
// }
>
<input
key={props.id}
id={props.id}
type='text'
value={props.currentObject.name}
onChange={(event) => {
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'name'
);
}}
/>
{props.urlFormVisible && (
<input
type='url'
value={props.currentObject.url}
onChange={(event) =>
userFunctions.modifyItem(
editorState,
props.currentObject,
stateModifier,
event,
'url'
)
}
/>
)}
</form>
</Fragment>
);
};
export default InputForm;
The function operates on the state and is bound in the master component
WHAT I HAVE TRIED
There are some similar posts on stack overflow but they do not seem to answer my problem.
Changed the Key value in the input (although my original version of this had a no ket defined)
Double checked that the modifyItem function is bound correctly - it looks like it is and I guess if it wasn't the state would not update at all
Tried simplifing code to reduce the number of functions needed to run
MOVING THE COMPONENT THE CODE HAS MADE
I am not sure why but the Higher order component was the problem
when I changed the config from
Page component
header(Modify)
InputForm
to
Page component
header
Modify
InputForm
/header
It worked
Any ideas why I had this problem?

How do I close a React Native Modal?

I am currently running into an issue where I can open my react-native modal just fine but once it's open I can't seem to close it. I just started using react-native about three weeks ago so I am extremely new to this.
I have tried implementing solutions that I've found online but nothing seemed to work for me. The opening functionality is great and seems to be working perfectly but when it comes to closing the modal none of the things I've tried have seemed to give the modal that ability. I have not been able to find a solid solution for my exact problem anywhere!
This is how I am opening the modal.
constructor(props) {
super(props);
this.state = {
refreshing: false,
display: false
};
}
triggerModal() {
this.setState(prevState => {
return {
display: true
}
});
}
<View>
<Button onPress = { () => this.triggerModal() } title = "Open Modal"></Button>
<DisplayModal display = { this.state.display } />
</View>
This is the modal itself, I am trying to use a button to close it.
import React from 'react'
import { Modal, View, Image, Text, StyleSheet, Button } from 'react-native';
const DisplayModal = (props) => (
<Modal visible={ props.display } animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button title="close" onPress = { () => !props.display }></Button>
</View>
</Modal>
)
export default DisplayModal;
As my familiarity with react-native is limited, it has been difficult wrapping my head around how some aspects of the framework function... I'm probably just making a dumb mistake somewhere in the code.
I appreciate any help with this problem!
You've almost got it, however we can make a few tweaks to get it working as you want.
As your DisplayModal has no state of its own, the state must be controlled by its parent component. So with that in mind we can do the following. Firstly pass an additional prop called closeDisplay to the DisplayModal. We're going to pass a function that sets the display property in state to false.
<DisplayModal
display={this.state.display}
closeDisplay={() => this.setState({display: false})} // <- we are passing this function
/>
Then in our DisplayModal component we are going to call that function to close the modal. So your DisplayModal component should look like this:
const DisplayModal = (props) => (
<Modal
visible={ props.display }
animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button
title="close"
onPress = { () => props.closeDisplay() }> // <- here we call the function that we passed
</Button>
</View>
</Modal>
)
Notice that the onPress function of the Button in the DisplayModal component, we are calling the function closeDisplay(). This function then sets the state in the parent component, which in turn gets passed back down to the DisplayModal component causing it to hide.

I can't edit Text Field in Material-UI

I developed a React App using Material-UI then I tried to create independent Components,
check the below independent components(<PanelDiv/>),
render() {
return (
<div className="panelDiv-component" style={{display:this.props.display}}>
<div className="panel-field-content">
<TextField
floatingLabelText={this.props.heading}
type={this.props.inputType}
value={this.props.value}
/>
{/* <input defaultValue className="form-control"></input>*/}
</div>
</div>
);
}
I tried to use the component like this,
<PanelDiv
heading='name'
inputType="text"
value={this.state.userData.name}
display={this.state.display[0]}
/>
But I can't update input field in this way.Also there is no error. How can i solve this? I want to update my input field.
Please check my input filed in the below image :
Because you are controlling the value of TextField by using value attribute but you are not updating the value by using onChange function, Since value of TextField is not changing so it becomes read only.
Solution:
Specify the onChange function with TextField and update the value inside that, Like this:
<TextField
floatingLabelText={this.props.heading}
type={this.props.inputType}
value={this.props.value}
onChange={this.props._change}
/>
Inside parent component:
_Change(event, value){
//update the value here
}
<PanelDiv
heading='name'
inputType="text"
value={this.state.userData.name}
_change={this._change}
display={this.state.display[0]}
/>
If you pass value as a prop to TextField you can't change that text!
On Material-UI official documentation they have used defaultValue="default val" as a prop.
So I used defaultValue as a prop! It worked fine for me!
<TextField
type="text"
defaultValue={this.props.val}
onChange={handleChange}
/>
Had the same error. I was not able to key in anything and it was because there was no name props included. example:
<TextField
type="email"
name='email'/>
Look at this example - https://jsfiddle.net/y857yeLq/
You should define a function, which is handles of text change and pass it to onChange property. In this example I used state for storing current text field value. I see, that you use props for that, but the principle is the same - function should update props in your case.
const { TextField, MuiThemeProvider, getMuiTheme } = MaterialUI;
class SliderExampleControlled extends React.Component {
state = {
value: '',
}
render() {
console.log(this.state);
return (
<div style={{width: '50%', margin: '0 auto'}}>
<TextField
hintText="Type here"
value={this.state.value}
onChange={(e) => this.setState(e.target.value)}
/>
</div>
);
}
}
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<SliderExampleControlled />
</MuiThemeProvider>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
I was running into this problem as well and I needed the onClick function to take more than just the event I also needed it to take the row number because my state had an array representing the rows in a table. I was able to do that using this chunk of code
onChange={(e) => this.nameChange(e.target.value, row.row_num)}
then in my function called nameChange I was able to update the value

React changing parent state during input onChange getting stuck

I am building a huge form which is made of atleast 50 inputs.
I have wrote a function in the form component that will save the value of the input to the form state:
PARENT FUNCTION
saveToState(details) {
const { company } = this.state;
company[details.part][details.element] = details.value;
this.setState({ company });
}
PASSING TO CHILD COMPONENT (INPUT)
<FieldInput
label="Name (as shown) *"
part="information"
element="displayName"
saveToState={this.saveToState}
/>
Here is the Input component:
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onChange={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}
In result whenever I type something in the input It's taking it 200-300ms to really display what I wrote in the input, the state is getting updates instantly but whenever I type a character I set the new state of the parent form and update it which updates the whole component. The only way i found around it is to use saveToState within the parent component without passing it down. but that would require 1000's of line of code, Is there any way around this? Thanks!
There are alot of ways you can solve this problem. The easiest one and the fastest one is to use onBlur instead of onChange that way setState will happen not when you key pressing in the input but when the input loses focus.
import React, { Component } from 'react';
export default class FieldInput extends Component {
render() {
const { label, part, element, saveToState } = this.props;
return (
<div className="field">
<label>{label}</label>
<div className="ui input">
<input
type="text"
name={`${part}[${element}]`}
onBlur={(e) => saveToState({
part,
element,
value: e.target.value
})}
/>
</div>
</div>
);
}
}

Categories