Conditional form rendering in react - javascript

So I am trying to render a form to start a new darts game conditionally depending on the game type selected. The form has a local state and "knows" which game is selected. So when selecting game "X01" I need a variant, inCondition and outCondition Dropdown whereas the game "Cricket" just needs one additional dropdown for variant (other values than x01 variants). I started to design a game form which looks like this:
gameform.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import SelectInputMultiple from '../common/SelectInputMultiple';
import SelectInput from '../common/SelectInput';
import { games, x01Variants, conditions, cricketVariants } from './assets';
export default class GameForm extends Component {
constructor(props) {
super(props);
this.players = props;
this.handleMultipleChange = this.handleMultipleChange.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
state = {
selectedPlayers: [],
game: 'x01',
x01variant: '501',
inCondition: 'straight',
outCondition: 'double',
cricketVariant: 'cutthroat',
errors: {}
};
formIsValid() {
const _errors = {};
if (this.state.selectedPlayers.length === 0)
_errors.selectedPlayers = 'You need to select at least one player';
this.setState({
errors: _errors
});
return Object.keys(_errors).length === 0;
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleMultipleChange = e => {
let _selectedPlayers = [...e.target.options]
.filter(o => o.selected)
.map(o => o.value);
this.setState(prevState => ({
selectedPlayers: { ...prevState.selectedPlayers, _selectedPlayers }
}));
};
handleSubmit = e => {
e.preventDefault();
if (!this.formIsValid()) return;
let _game = {
selectedPlayers: this.state.selectedPlayers,
game: this.state.game,
x01Variant: this.state.x01variant,
inCondition: this.state.inCondition,
outCondition: this.state.outCondition,
cricketVariant: this.state.cricketVariant
};
this.props.onSubmit(_game);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<SelectInputMultiple
id="players"
label="Players"
name="players"
onChange={this.handleMultipleChange}
options={this.props.players}
error={this.state.errors.selectedPlayers}
/>
<SelectInput
id="game"
label="Game Type"
name="game"
onChange={this.handleChange}
options={games}
value={this.state.game}
error={this.state.errors.game}
/>
<SelectInput
id="x01Variant"
label="X01 Variants"
name="x01Variant"
onChange={this.handleChange}
options={x01Variants}
value={this.state.x01variant}
error={this.state.errors.x01Variants}
/>
<SelectInput
id="inCondition"
label="In Condition"
name="inCondition"
onChange={this.handleChange}
options={conditions}
value={this.state.inCondition}
error={this.state.errors.condition}
/>
<SelectInput
id="outCondition"
label="Out Condition"
name="outCondition"
onChange={this.handleChange}
options={conditions}
value={this.state.outCondition}
error={this.state.errors.condition}
/>
<SelectInput
id="cricketVariant"
label="Variant"
name="cricketVariant"
onChange={this.handleChange}
options={cricketVariants}
value={this.state.cricketVariant}
error={this.state.errors.cricketVariant}
/>
<input type="submit" value="Start Game" className="btn btn-primary" />
</form>
);
}
}
GameForm.propTypes = {
onSubmit: PropTypes.func.isRequired,
players: PropTypes.array
};
So my goal is to just show the corresponding fields linked to the game type. How can I do that depending on this.state.game?
Thanks in advance for any hint!

You could just conditionally render the variant select input when the game mode is set to "Cricket"
{this.state.game === 'Cricket' && (
<SelectInput />
)}
The reason you can write it as such is because in JavaScript the && operator basically returns the first value if it is falsy and the second value if the first value is truthy. i.e.
a && b will return 'a' if 'a' is falsy, and it will return 'b' if 'a' is truthy.
So in this case when this.state.game === 'Cricket' then the JSX code will be returned hence rendering the form input.
One additional tip!
If you want to render one of two JSX elements based on a condition, you can just use a ternary expression!
{this.state.game === 'Cricket' ? (
// Renders when game mode is 'Cricket'
<Cricket />
) : (
// Renders when game mode is NOT 'Cricket'
<SomeOtherGame />
)}

So in this case you need a generic and simple solution, so in your state since you have only limited number of games you can have a state variable as shown below.
state = {selectBoxEnabled : {xo1: ['xo1variant', 'inCondition', 'outCondition'], cricket: ['variant'], 'split-score': ['split-score_select']} }
So the above selectBoxEnabled field has the same key as in your dropdown field
Game Select Box : xo1, cricket, split-score has option values
So when the user choose an option you will be getting the key as xo1 Assume
Now you need to find the fields of xo1 game
const findFields = (currentGameKey) => {
let selectBoxKeys = selectBoxEnabled[Object.keys(selectBoxEnabled).find(key => key === currentGameKey)]
this.setState(selectBoxKeys)
}
Now you know which are the fields to be shown since you are using same component SelectInput , you can do a simple array.map and render the fields from the config.
I hope this will give a better understanding of the problem

I was doing my project and in my project, and I faced such a situation
enter image description here
if a user selects one of the there options as an option
if the option is one of the three except they select the form lookalike this
enter image description here
else if they select DDP the form will be changed by adding a new input field
enter image description here
I handle it using a ternary operator based on the input user selected and it works for me
if it's helpful the code snippet will be here
{
ddp !== undefined ? (
incotermData == ddp ? (
<Controls.Input
name="exchangeRate"
label="Exchange Rate"
onChange={(e) => setExchangeRate(e.target.value)}
/>
) : (
<></>
)
) : (
<></>
);
}

Related

Hide Child Component Element After onClick using ReactJS

I'm new to React, learning by coding, here i have component A, which has select element with menuItems (all material ui), when user clicks select element and chooses from drop down, right after user has chosen whole component should go display:none, is this possible ? i mean user should not be able to see select element anymore on the page
English is not my mother language, so there might be mistakes.
suggestions/help is appreciated.
component A:
const A: React.FC<AProps> = (props) => {
const handleChange = (e: React.ChangeEvent<{ value: unknown }>) => {
const site = e.target.value as string;
dispatch(changeActiveSite(site));
if (site) {
dispatch(getAnalysers(site));
} else {
dispatch(clearSiteData(site));
}
};
const sites = [
{
ident: "",
name: "None",
},
].concat(sitess);
return (
<React.Fragment>
<FormControl className={classes.formControl}>
<InputLabel id="site-select-input-label">site</InputLabel>
<Select
id="site-select"
value={currentSiteId}
labelId="site-select-input-label"
onChange={(e) => handleChange(e)}
>
{sites.map((site) => {
return (
<MenuItem key={site.ident} value={site.ident}>
{site.name}
</MenuItem>
);
})}
</Select>
</FormControl>
</React.Fragment>
);
};
that component is in component B like this: <div > <A site={site} /> </div>
Let's imagine your MenuItem.js, specifically, its render() and constructor(). You'll want it to be able to be hidden/not-displayed, or visible/displayed. Use a state attribute for hidden to control this, your render will probably look like...
constructor(props) {
super(props);
this.state = {
'hidden':false,
};
}
render () {
if(this.state.hidden) {
return '';
}
return (
<div
onClick={(e) => this.handleOnClick(e)}
>
{this.props.value}
</div>
);
}
Notice I also added a handleOnClick(e) handler up above! That will simply call this.setState({'hidden':true}), so like...
handleOnClick(e) {
this.setState({'hidden':true});
}
I have an answer to a similar question elsewhere, if it might also help: How to set one component's state from another component in React

Print TextField on button click - printing incorrectly

I'm attempting to incorporate a function in my react app, where a user will input a search term, and when the button is pressed it will go search it. (text input and buttons are using material-ui components) For now I just want it to print the term to the console log to check its okay. When running the below code, the output is: "[object Object]. Does anyone know why that is?
import React from 'react';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
class searchText extends React.Component {
state = {
searchTerm: null,
}
handleClick = (searchTerm) => {
this.setState({ searchTerm });
}
render() {
const { searchTerm } = this.state;
return (
<div className ="Search">
<TextField
id = "outlined-search"
label="Enter Query"
type = "search"
className ="Search"
variant="filled"
value = {searchTerm}
/>
<Button
onClick={() =>{console.log("search: " +{searchTerm}); }}
varient ="contained"
classname="goButton" >
Enter Query Here!
<Button/>
</div>
);
}
}
export default Search
update: fixed typos
your have not any function to get the input value and set that value in state.
Try this.
// function for TextField Value Change
getvalue=(event)=>{
this.setState({
searchTerm:event.target.value
})
}
// function for button click
handleClick = () => {
console.log(this.state.searchTerm)
}
<TextField
id = "outlined-search"
label="Enter Query"
type = "search"
className ="Search"
variant="filled"
value = {this.state.searchTerm}
onChange={this.getvalue}
/>
The + searchTem coerces the object into a string, which in your case is [object Object]
Just change it to:
console.log("search: ", searchTerm)
You can read more about it here: https://developer.mozilla.org/en-US/docs/Web/API/console#Outputting_text_to_the_console
Right now your'e not calling handleClick as you just console log it. That means that you're not changing/setting the state.searchTerm to anything. It's the handleClick that will update the state. So the value will always be null. Also you have a typo in your handleClick:
Your parameter says "serchTerm" ... it should say "searchTerm". You missed an a there ;)
Classes use PascalCase as they are components, not simple functions. The Buttons component were badly terminated and maybe you can just simply call your function handler when the user clicks on the button and then show the state on the console
class SearchText extends React.Component {
state = {
searchTerm: null,
}
handleClick = (term) => {
this.setState({ searchTerm: term });
console.log(this.state.searchTerm)
}
render() {
const { searchTerm } = this.state;
return (
<div className ="Search">
<TextField
id = "outlined-search"
label="Enter Query"
type = "search"
className ="Search"
variant="filled"
value = {searchTerm}
/>
<Button
onClick={this.handleClick}
varient ="contained"
classname="goButton" >
Enter Query Here!
</Button>
</div>
);
}
}
Hope helps :)

React: How to set state of object property?

I am a beginner with React and I have been using the NPM package react-
tabs in order to display tabs on my site.
I have been trying to allow the
user to update descriptions of their profiles using a checkbox to open an input type=text tag allowing the user to put in their own details for their profile.
However, I am struggling to set the state of the description for the tab using the handleDesc function. I am not sure how I can update the state of the descriptions for each tab. I keep getting the error "A component is changing a controlled input of type checkbox to be uncontrolled."
Can anyone help me?
class EditSite extends React.Component {
constructor(props) {
super(props);
this.state = {About: true,
"Opening Hours": true,
Contact: true,
Menu: false,
Offers: false,
"External Links": true,
tabOptions: {
About: {desc:'1'},
"Opening Hours": {desc: '2'},
Contact: {desc:'3'},
Menu: {desc:'4'},
Offers: {desc:'5'},
"External Links": {desc:'6'},
}
}
Below is the function (handleDesc) I am struggling with to set the state of the descriptions.
handleDesc = (event) => {
let tabOptions = {...this.state.tabOptions[event.target.name]};
tabOptions = event.target.value;
console.log(tabOptions);
this.setState({tabOptions: [event.target.name]});
}
render() {
const links = [];
const tabs = [];
const tabPanels = [];
The second input tag is where I would like the user to be able to add their own details to the specific tab.
Object.keys(this.state.tabOptions).forEach(name => {
links.push(
<div>
<label key={name}>{name}</label>
<input
type="checkbox"
checked={this.state[name]}
name={name}
onChange={this.handleCheckClicked}
/>
{ this.state[name] === true ? (
<input
name={name}
type='text'
onChange={this.handleDesc}
/>
) : null }
</div>
);
if (!this.state[name]) return;
const { desc } = this.state.tabOptions[name];
tabs.push(
<Tab>
<h3>{name}</h3>
</Tab>
);
tabPanels.push(
<TabPanel>
{desc}
</TabPanel>
);
});
SetState wants a new object. So settings just a property of a nested object isn’t possible in that way. What you could do is copying the tabOptions and changing the properties you want before you pass it to setState.
example
handleDesc = (event) => {
const newTabOptions = {
...this.state.tabOptions,
[event.target.name]: {desc: event.target.value}
this.setState({tabOptions: newTabOptions});
}
A quick way to resolve this would be to revise that code that binds the <input /> element's onChange handler to the following:
links.push(
<div>
<label key={name}>{name}</label>
<input
type="checkbox"
checked={this.state[name]}
name={name}
onChange={ (event) => this.handleCheckClicked(event) /* <-- update this also */ }
/>
{ this.state[name] === true ? (
<input
name={name}
type='text'
onChange={ (event) => this.handleDesc(event) /* [ UPDATE HERE ] */ }
/>
) : null }
</div>
);
By declaring an arrow function inline like this:
onChange={ (event) => this.handleDesc(event) }
it will set the context of the handleDesc function to be the <EditSite /> component (currently, the context will be the global window object). This means that when you access this.state, or call a method like this.setState() inside of the handleDesc, it will work as expected.
For more information on this subtle characteristic of "arrow functions" see this article.
Update
Also, consider updating your handleDesc method like so to correctly mutate your state:
handleDesc = (event) => {
let tabOptions = {...this.state.tabOptions[event.target.name]};
tabOptions = event.target.value;
console.log(tabOptions);
this.setState({tabOptions}); // <--- Update this line
}
Update 2
On further investigation (into react's source code), it would see the problem is that your <input /> element does not have a checked attribute when this.state[name] === true is satisfied. If you apply a dummy attribute/value pair of checked={ true } on the input rendered in the this.state[name] === true case, this warning message should stop showing:
links.push(
<div>
<label key={name}>{name}</label>
<input
type="checkbox"
checked={this.state[name]}
name={name}
onChange={this.handleCheckClicked}
/>
{ this.state[name] === true ? (
<input
name={name}
type='text'
checked={ true } /* <-- Update this line, don't include this comment in code */
onChange={this.handleDesc}
/>
) : null }
</div>
);

Nesting redux form fields for grouped validation

Is the below considered a valid use of redux-form Fields?
const ValidatedFieldGroup = (props) => {
const {meta: {touched, error}} = props
return (
<div className={touched && error ? 'has-error' : ''}>
<Field name="one" .... />
<Field name="two" .... />
</div>
)
}
const MyMainComponent = (props) => {
return <Field name="twofields"
component={ValidatedFieldGroup}
validate={myValidator} .... />
}
const myValidator = (value, allValues) => {
return (
allValues.one === "pretend-this-is-valid" && allValues.two === "pretend-this-is-valid"
) ? undefined : 'One of the fields is invalid - sort it out!'
}
A 'valueless' parent Field is implemented solely to hook into the sync field level validation pipeline. The props of this component can then be used to alter the UI / state of it's children (which in turn contain some RF Fields implementing the actual form values)
Real-world example = assume there are a group of five check boxes... if at least TWO of them are not checked then they should ALL be wrapped in a 'red bordered' div.
In seems to work thus far, but I am conscious that there may be an easier / better / correct way to achieve the same result or that I could in fact be setting myself up for future troubles!!
Thanks in advance.
Although this work-around results in the desired UI (namely, a single div with the correct class), you'll end up with three fields in the redux-form store, one, two, and twofields, which seems undesirable. Presumably, you'll never do anything with the field twofields on the backend, since it's only used for presentation. This goes against the idea that the redux-form store should map to the fields in your backend (DB, or whatever...).
You could instead use the Fields component, so that you only register the one and two fields, which is more consistent:
import { Fields, ...} from "redux-form";
const renderValidatedFields = fields => {
const { one, two } = fields;
const showError = (one.meta.touched && one.meta.error) || (two.meta.touched && two.meta.error);
return (
<div className={showError ? 'has-error' : ''}>
<input {...one.input} type="checkbox" />
<input {...two.input} type="checkbox" />
</div>
)
}
export default MyMainFieldComponent = props => {
return <Fields names={["one", "two"]} component={renderValidatedFields} />
}
Then put your validator against redux-form in the config:
import React from "react";
import { reduxForm } from "redux-form";
import MyMainFieldComponent from "./MyMainFieldComponent";
// validate the whole form
const myValidator = values => {
const msg = "Invalid selection.";
const errors = {};
if (!values.one) {
errors.one = msg;
}
if (!values.two) {
errors.two = msg;
}
return errors;
}
...
let MyForm = props => {
...
return (
<form ...>
<MyMainFieldComponent />
</form>
)
}
MyForm = reduxForm({
...,
validate: myValidator
})(MyForm);
export default MyForm;

React.js - input losing focus when rerendering

I am just writing to text input and in onChange event I call setState, so React re-renders my UI. The problem is that the text input always loses focus, so I need to focus it again for each letter :D.
var EditorContainer = React.createClass({
componentDidMount: function () {
$(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
},
componentDidUpdate: function () {
console.log("zde");
$(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
},
changeSelectedComponentName: function (e) {
//this.props.editor.selectedComponent.name = $(e.target).val();
this.props.editor.forceUpdate();
},
render: function () {
var style = {
height: this.props.height + 'px'
};
return (
<div className="container" style={style}>
<div className="row">
<div className="col-xs-6">
{this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
{this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
</div>
<div className="col-xs-6">
<ComponentTree editor={this.props.editor} components={this.props.components}/>
</div>
</div>
</div>
);
}
});
Without seeing the rest of your code, this is a guess.
When you create a EditorContainer, specify a unique key for the component:
<EditorContainer key="editor1"/>
When a re-rendering occurs, if the same key is seen, this will tell React don't clobber and regenerate the view, instead reuse. Then the focused item should retain focus.
I keep coming back here again and again and always find the solution to my elsewhere at the end.
So, I'll document it here because I know I will forget this again!
The reason input was losing focus in my case was due to the fact that I was re-rendering the input on state change.
Buggy Code:
import React from 'react';
import styled from 'styled-components';
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
const Container = styled.div``;
const Input = styled.input``;
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
So, the problem is that I always start coding everything at one place to quickly test and later break it all into separate modules.
But, here this strategy backfires because updating the state on input change triggers render function and the focus is lost.
Fix is simple, do the modularization from the beginning, in other words, "Move the Input component out of render function"
Fixed Code
import React from 'react';
import styled from 'styled-components';
const Container = styled.div``;
const Input = styled.input``;
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
Ref. to the solution: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947
If it's a problem within a react router <Route/> use the render prop instead of component.
<Route path="/user" render={() => <UserPage/>} />
The loss of focus happens because the component prop uses React.createElement each time instead of just re-rendering the changes.
Details here: https://reacttraining.com/react-router/web/api/Route/component
I had the same symptoms with hooks. Yet my problem was defining a component inside the parent.
Wrong:
const Parent =() => {
const Child = () => <p>Child!</p>
return <Child />
}
Right:
const Child = () => <p>Child!</p>
const Parent = () => <Child />
My answer is similar to what #z5h said.
In my case, I used Math.random() to generate a unique key for the component.
I thought the key is only used for triggering a rerender for that particular component rather than re-rendering all the components in that array (I return an array of components in my code). I didn't know it is used for restoring the state after rerendering.
Removing that did the job for me.
Applying the autoFocus attribute to the input element can perform as a workaround in situations where there's only one input that needs to be focused. In that case a key attribute would be unnecessary because it's just one element and furthermore you wouldn't have to worry about breaking the input element into its own component to avoid losing focus on re-render of main component.
What I did was just change the value prop to defaultValue and second change was onChange event to onBlur.
I got the same behavior.
The problem in my code was that i created a nested Array of jsx elements like this:
const example = [
[
<input value={'Test 1'}/>,
<div>Test 2</div>,
<div>Test 3</div>,
]
]
...
render = () => {
return <div>{ example }</div>
}
Every element in this nested Array re-renders each time I updated the parent element. And so the inputs lose there "ref" prop every time
I fixed the Problem with transform the inner array to a react component
(a function with a render function)
const example = [
<myComponentArray />
]
...
render = () => {
return <div>{ example }</div>
}
EDIT:
The same issue appears when i build a nested React.Fragment
const SomeComponent = (props) => (
<React.Fragment>
<label ... />
<input ... />
</React.Fragment>
);
const ParentComponent = (props) => (
<React.Fragment>
<SomeComponent ... />
<div />
</React.Fragment>
);
I solved the same issue deleting the key attribute in the input and his parent elements
// Before
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
key={ Math.random }
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
// After
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
The answers supplied didn't help me, here was what I did but I had a unique situation.
To clean up the code I tend to use this format until I'm ready to pull the component into another file.
render(){
const MyInput = () => {
return <input onChange={(e)=>this.setState({text: e.target.value}) />
}
return(
<div>
<MyInput />
</div>
)
But this caused it to lose focus, when I put the code directly in the div it worked.
return(
<div>
<input onChange={(e)=>this.setState({text: e.target.value}) />
</div>
)
I don't know why this is, this is the only issue I've had with writing it this way and I do it in most files I have, but if anyone does a similar thing this is why it loses focus.
If the input field is inside another element (i.e., a container element like <div key={"bart"}...><input key={"lisa"}...> ... </input></div>-- the ellipses here indicating omitted code), there must be a unique and constant key on the container element (as well as on the input field). Elsewise, React renders up a brand new container element when child's state is updated rather than merely re-rendering the old container. Logically, only the child element should be updated, but...
I had this problem while trying to write a component that took a bunch of address information. The working code looks like this
// import react, components
import React, { Component } from 'react'
// import various functions
import uuid from "uuid";
// import styles
import "../styles/signUp.css";
export default class Address extends Component {
constructor(props) {
super(props);
this.state = {
address1: "",
address2: "",
address1Key: uuid.v4(),
address2Key: uuid.v4(),
address1HolderKey: uuid.v4(),
address2HolderKey: uuid.v4(),
// omitting state information for additional address fields for brevity
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
event.preventDefault();
this.setState({ [`${event.target.id}`]: event.target.value })
}
render() {
return (
<fieldset>
<div className="labelAndField" key={this.state.address1HolderKey} >
<label className="labelStyle" for="address1">{"Address"}</label>
<input className="inputStyle"
id="address1"
name="address1"
type="text"
label="address1"
placeholder=""
value={this.state.address1}
onChange={this.handleChange}
key={this.state.address1Key} ></input >
</div>
<div className="labelAndField" key={this.state.address2HolderKey} >
<label className="labelStyle" for="address2">{"Address (Cont.)"}</label>
<input className="inputStyle"
id="address2"
name="address2"
type="text"
label="address2"
placeholder=""
key={this.state.address2Key} ></input >
</div>
{/* omitting rest of address fields for brevity */}
</fieldset>
)
}
}
Sharp-eyed readers will note that <fieldset> is a containing element, yet it doesn't require a key. The same holds for <> and <React.Fragment> or even <div> Why? Maybe only the immediate container needs a key. I dunno. As math textbooks say, the explanation is left to the reader as an exercise.
I had this issue and the problem turned out to be that I was using a functional component and linking up with a parent component's state. If I switched to using a class component, the problem went away. Hopefully there is a way around this when using functional components as it's a lot more convenient for simple item renderers et al.
I just ran into this issue and came here for help. Check your CSS! The input field cannot have user-select: none; or it won't work on an iPad.
The core reason is: When React re-render, your previous DOM ref will be invalid. It mean react has change the DOM tree, and you this.refs.input.focus won't work, because the input here doesn't exist anymore.
For me, this was being caused by the search input box being rendered in the same component (called UserList) as the list of search results. So whenever the search results changed, the whole UserList component rerendered, including the input box.
My solution was to create a whole new component called UserListSearch which is separate from UserList. I did not need to set keys on the input fields in UserListSearch for this to work. The render function of my UsersContainer now looks like this:
class UserContainer extends React.Component {
render() {
return (
<div>
<Route
exact
path={this.props.match.url}
render={() => (
<div>
<UserListSearch
handleSearchChange={this.handleSearchChange}
searchTerm={this.state.searchTerm}
/>
<UserList
isLoading={this.state.isLoading}
users={this.props.users}
user={this.state.user}
handleNewUserClick={this.handleNewUserClick}
/>
</div>
)}
/>
</div>
)
}
}
Hopefully this helps someone too.
I switched value prop to defaultValue. That works for me.
...
// before
<input value={myVar} />
// after
<input defaultValue={myVar} />
My problem was that I named my key dynamically with a value of the item, in my case "name" so the key was key={${item.name}-${index}}. So when I wanted to change the input with item.name as the value, they key would also change and therefore react would not recognize that element
included the next code in tag input:
ref={(input) => {
if (input) {
input.focus();
}
}}
Before:
<input
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
After:
<input
ref={(input) => {
if (input) {
input.focus();
}
}}
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
I had a similar issue, this is fixed it.
const component = () => {
return <input onChange={({target})=>{
setValue(target.vlaue)
}
} />
}
const ThisComponentKeptRefreshingContainer = () => {
return(
<component />
)
}
const ThisContainerDidNot= () => {
return(
<> {component()} </>
)
}
As the code illustrate calling the component child like an element gave that re-rendering effect, however, calling it like a function did not.
hope it helps someone
I had the same problem with an html table in which I have input text lines in a column. inside a loop I read a json object and I create rows in particular I have a column with inputtext.
http://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/
I managed to solve it in the following way
import { InputTextComponent } from './InputTextComponent';
//import my inputTextComponent
...
var trElementList = (function (list, tableComponent) {
var trList = [],
trElement = undefined,
trElementCreator = trElementCreator,
employeeElement = undefined;
// iterating through employee list and
// creating row for each employee
for (var x = 0; x < list.length; x++) {
employeeElement = list[x];
var trNomeImpatto = React.createElement('tr', null, <td rowSpan="4"><strong>{employeeElement['NomeTipologiaImpatto'].toUpperCase()}</strong></td>);
trList.push(trNomeImpatto);
trList.push(trElementCreator(employeeElement, 0, x));
trList.push(trElementCreator(employeeElement, 1, x));
trList.push(trElementCreator(employeeElement, 2, x));
} // end of for
return trList; // returns row list
function trElementCreator(obj, field, index) {
var tdList = [],
tdElement = undefined;
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}
//updateImpactCompositeNote={tableComponent.updateImpactCompositeNote}
/>
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);
var trComponent = createClass({
render: function () {
return React.createElement('tr', null, tdList);
}
});
return React.createElement(trComponent);
} // end of trElementCreator
});
...
//my tableComponent
var tableComponent = createClass({
// initial component states will be here
// initialize values
getInitialState: function () {
return {
impattiComposite: [],
serviceId: window.sessionStorage.getItem('serviceId'),
serviceName: window.sessionStorage.getItem('serviceName'),
form_data: [],
successCreation: null,
};
},
//read a json data soure of the web api url
componentDidMount: function () {
this.serverRequest =
$.ajax({
url: Url,
type: 'GET',
contentType: 'application/json',
data: JSON.stringify({ id: this.state.serviceId }),
cache: false,
success: function (response) {
this.setState({ impattiComposite: response.data });
}.bind(this),
error: function (xhr, resp, text) {
// show error to console
console.error('Error', xhr, resp, text)
alert(xhr, resp, text);
}
});
},
render: function () {
...
React.createElement('table', {style:null}, React.createElement('tbody', null,trElementList(this.state.impattiComposite, this),))
...
}
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read //from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}//impattiComposite = my json data source
/>//end my input text
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);//add a component
//./InputTextComponent.js
import React from 'react';
export class InputTextComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
idImpatto: props.idImpatto,
value: props.value,
noteType: props.noteType,
_impattiComposite: props.impattiComposite,
};
this.updateNote = this.updateNote.bind(this);
}
//Update a inpute text with new value insert of the user
updateNote(event) {
this.setState({ value: event.target.value });//update a state of the local componet inputText
var impattiComposite = this.state._impattiComposite;
var index = this.state.idImpatto - 1;
var impatto = impattiComposite[index];
impatto[this.state.noteType] = event.target.value;
this.setState({ _impattiComposite: impattiComposite });//update of the state of the father component (tableComponet)
}
render() {
return (
<input
className="Form-input"
type='text'
value={this.state.value}
onChange={this.updateNote}>
</input>
);
}
}
Simple solution in my case:
<input ref={ref => ref && ref.focus()}
onFocus={(e)=>e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length)}
/>
ref triggers focus, and that triggers onFocus to calculate the end and set the cursor accordingly.
The issue in my case was that the key prop values I was setting on the InputContainer component and the input fields themselves were generated using Math.random(). The non-constant nature of the values made it hard for track to be kept of the input field being edited.
For me I had a text area inside a portal. This text area was loosing focus. My buggy portal implementation was like this:
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const divRef = useRef(document.createElement('div'));
useEffect(()=>{
const ref = divRef.current;
modalDOM?.appendChild(ref);
return ()=>{
modalDOM?.removeChild(ref);
}
});
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
}
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
divRef.current)
)
}
const Parent = ({content: string}: ParentProps) => {
const [content, setContent] = useState<string>(content);
const onChangeFile = (e: React.MouseEvent) => {
setContent(e.currentTarget.value);
}
return (
<Modal>
<textarea
value={content}
onChange={onChangeFile}>
</textarea>
</Modal>
)
}
Turned out following implementation worked correctly, here I am directly attaching modal component to the DOM element.
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
modalDOM || document.body)
)
}
Turns out I was binding this to the component which was causing it to rerender.
I figured I'd post it here in case anyone else had this issue.
I had to change
<Field
label="Post Content"
name="text"
component={this.renderField.bind(this)}
/>
To
<Field
label="Post Content"
name="text"
component={this.renderField}
/>
Simple fix since in my case, I didn't actually need this in renderField, but hopefully me posting this will help someone else.
Changing text in the input of some control can cause parent control rerendering in some cases (according to binding to props).
In this case focus will be lost. Editing should not has effect to parent container in DOM.

Categories

Resources