why destroyOnClose={true} not working in React - reactjs

I am developing a React hook based functional application with TypeScript and I am using modal from ant design. I'm submitting a form through modal for a table. So, the modal will be called for more than once to fill-up different rows of the table.
The problem is, when the modal is popping up for the second, third or lateral times, it's always carrying the previous values.
To avoid that, I set in the modal EnableViewState="false" , it didn't work . I set
destroyOnClose={true}. It didn't work. In the modal documentation, it is written when destroyOnClose doesn't work then we need to use . But where to define it ? Because, when I am setting up as,
<Form onSubmit={props.inputSubmit} preserve={false} in my modal form, I'm getting an error saying Type '{ children: Element[]; onSubmit: any; preserve: boolean; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<Form>......?
what do you use so that every time the modal reloads, it reloads as empty ? I don't want to assign the state in the form value fields of the input. Is there any other option such as, destroyOnClose={true} ?
Here is my modal,
<Form onSubmit={props.inputSubmit}>
<Row>
<Col span={10}>
<Form.Item>
<Text strong={true}>Article name: </Text>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item>
<Input
style={{ backgroundColor: '#e6f9ff' }}
name="articleName"
onChange={props.handleArticleModalInput}
/>
</Form.Item>
</Col>
</Row>
</Form>
Here is from where the modal is getting called,
return (
<>
<ArticleTableModal
destroyOnClose={true}
isVisible={modalVisibilty}
inputSubmit={inputSubmit}
handleCancel={handleCancel}
filledData={fetchedData}
articleNumber={articleNumber}
handleArticleModalInput={handleArticleModalInput}
/>
<Table
pagination={false}
dataSource={articleDataSource}
columns={articleColumns}
scroll={{ y: 400 }}
bordered
/>
</>
)
Any help is much appreciated.

You need to generate dynamic keys for the fields in the form on each modal launch.
Here's a sandbox to play around. If you don't make any changes to the key, the modal retains values inside it. If you change key and launch modal, the value gets cleared.
Sandbox Link
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Modal, Button, Input } from "antd";
class App extends React.Component {
state = { visible: false, theKey: "dummy" };
showModal = () => {
this.setState({
visible: true
});
};
handleOk = (e) => {
console.log(e);
this.setState({
visible: false
});
};
handleCancel = (e) => {
console.log(e);
this.setState({
visible: false
});
};
handleChange = ({ target: { value } }) => {
this.setState({ theKey: value });
};
render() {
return (
<>
<Input onChange={this.handleChange} placeholder="key for input field"/>
<br />
<Button type="primary" onClick={this.showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={this.state.visible}
onOk={this.handleOk}
onCancel={this.handleCancel}
>
<Input
key={this.state.theKey}
style={{ backgroundColor: "#e6f9ff" }}
name="articleName"
/>
</Modal>
</>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));

Here we'll use a custom hook that wraps a ModalDialog component from somewhere else (like a 3rd party UI library) and gives us back a tuple of a setter and a self-contained component/null. Hooks make this neater but you can still accomplish all of this with class components at the cost of a little verbosity. Since you tagged Typescript this should all be straightforward but you may have to specify that your use of useState is useState<React.ReactChild>(); to avoid type errors.
const useDialog = (ModalComponent) => {
const [modalDialogState, setModalDialogState] = useState();
return modalDialogState
? [
setModalDialogState,
// You may have to tweak this a bit depending on
// how your library works.
() => (
<ModalComponent onClose={() => setModalDialogState('')>
{modalDialogState}
</ModalComponent>
),
]
: [setModalDialogState, null];
};
const MyComponent = () => {
const [setModal, Modal] = useDialog(WhateverLibraryComponent);
useEffect(() => {
// Cleanup function, run on unMount and clears dialog contents.
return () => setModal('');
}, [setModal]);
return Modal
? (
<>
<Modal />
/* Normal render stuff */
</>
)
// You can optionally render a button here with onClick
// set to a function that calls setModal with some
// appropriate contents.
: (/* Normal render stuff */)
};

Related

How to disable the Text field name is disappearing when we moved out the input filed box in react js

I have made autocomplete features using Downshift using react js. But the problem is when I am searching for something its input field value is disappearing when I click on the outside. Here is the sample code.
import logo from './logo.svg';
import './App.css';
import React, { useState } from "react";
import Highlighter from "react-highlight-words";
import Downshift from "downshift";
import axios from 'axios';
function App() {
const [names, setnames] = useState([{
const [searchTerm, setSearchTerm] = useState('')
const [movie, setmovie] = useState([])
fetchMovies = fetchMovies.bind(this);
inputOnChange = inputOnChange.bind(this);
function inputOnChange(event) {
if (!event.target.value) {
return;
}
fetchMovies(event.target.value);
}
function downshiftOnChange(selectedMovie) {
alert(`your favourite movie is ${selectedMovie.title}`);
}
function fetchMovies(movie) {
const moviesURL = `https://api.themoviedb.org/3/search/movie?api_key=1b5adf76a72a13bad99b8fc0c68cb085&query=${movie}`;
axios.get(moviesURL).then(response => {
setmovie(response.data.results);
// this.setState({ movies: response.data.results });
});
}
return (
<Downshift
onChange={downshiftOnChange}
itemToString={item => (item ? item.title : "")}
>
{({
selectedItem,
getInputProps,
getItemProps,
highlightedIndex,
isOpen,
inputValue,
getLabelProps
}) => (
<div>
<label
style={{ marginTop: "1rem", display: "block" }}
{...getLabelProps()}
>
Choose your favourite movie
</label>{" "}
<br />
<input
{...getInputProps({
placeholder: "Search movies",
onChange: inputOnChange
})}
/>
{isOpen ? (
<div className="downshift-dropdown">
{movie
.filter(
item =>
!inputValue ||
item.title
.toLowerCase()
.includes(inputValue.toLowerCase())
)
.slice(0, 10)
.map((item, index) => (
<div
className="dropdown-item"
{...getItemProps({ key: index, index, item })}
style={{
backgroundColor:
highlightedIndex === index ? "lightgray" : "white",
fontWeight: selectedItem === item ? "bold" : "normal"
}}
>
{item.title}
</div>
))}
</div>
) : null}
</div>
)}
</Downshift>
);
}
export default App;
This is the sample code I have written. Also, when I click shift+home, it is also not working.
Problem 1: when the user clicked the outside text field value whatever I searched this is disappearing.
Problem 2: shift + home is not working also.
Anyone has any idea how to solve this problem?
when the user clicked the outside text field value whatever I searched this is disappearing.
One way you could do it is to set the stateReducer on the Downshift component:
This function will be called each time downshift sets its internal state (or calls your onStateChange handler for control props). It allows you to modify the state change that will take place which can give you fine grain control over how the component interacts with user updates without having to use Control Props. It gives you the current state and the state that will be set, and you return the state that you want to set.
state: The full current state of downshift.
changes: These are the properties that are about to change. This also has a type property which you can learn more about in the stateChangeTypes section.
function stateReducer(state, changes) {
switch (changes.type) {
case Downshift.stateChangeTypes.mouseUp:
return {
...changes,
isOpen: true,
inputValue: state.inputValue,
};
default:
return changes;
}
}
This way if you click outside the text field the dropdown will stay open and the input value won't be reset.
For a list of all state change types see the documentation here
You might also be able to get something working using the onBlur prop on the input, but I didn't get that working.

Custom HTML Button wrapped in Link not working

I have made a custom Button component that returns HTML button. Now I am using this Button inside Link from next-routes. The problem is that it does not work this way. Weird thing is that button works correctly if I use HTML button inside Link. However, both button are being rendered in the DOM in exact same way. Following is the code:
// Button.js
import React from "react";
import classNames from "classnames";
const Button = ({
children,
newClass = "",
onClickHandler = () => { },
isSubmitting = false,
inlineBtn = true,
disabled,
primary,
secondary,
basic,
notCentered = true,
shaded,
miniLoader,
type = "button",
isTransparent,
fontClass = "",
small
}) => {
return (
<React.Fragment>
<button
className={classNames(
`${isTransparent ? 'btn-transparent' : 'btn'} ${fontClass} ${newClass}`,
{
"btn-block": !inlineBtn,
"col-mx-auto": !notCentered,
"btn-primary": primary,
"btn-secondary": secondary,
"btn-basic": basic,
shaded: shaded,
loading: isSubmitting,
"loading-sm": miniLoader,
"btn-sm": small
}
)}
disabled={disabled}
type={type}
onClick={onClickHandler}
>
<span>{children}</span>
</button>
</React.Fragment>
);
};
export { Button };
The following does not work:
<Link route="/register/location">
<Button basic small>
Sign Up
</Button>
</Link>
The following works fine:
<Link route="/register/location">
<button className="btn btn-basic btn-sm" type="button" onClick={() => { }}>
<span>Sign Up</span>
</button>
</Link>
You can update your Button component as the following.
const Button = ({
as = "button",
children,
newClass = "",
onClickHandler = () => {},
isSubmitting = false,
inlineBtn = true,
disabled,
primary,
secondary,
basic,
notCentered = true,
shaded,
miniLoader,
type = "button",
isTransparent,
fontClass = "",
small,
...rest
}) => {
const Wrapper = as;
return (
<Wrapper
className={classNames(
`${isTransparent ? "btn-transparent" : "btn"} ${fontClass} ${newClass}`,
{
"btn-block": !inlineBtn,
"col-mx-auto": !notCentered,
"btn-primary": primary,
"btn-secondary": secondary,
"btn-basic": basic,
shaded: shaded,
loading: isSubmitting,
"loading-sm": miniLoader,
"btn-sm": small
}
)}
disabled={disabled}
type={type}
onClick={onClickHandler}
{...rest}
>
<span>{children}</span>
</Wrapper>
);
};
This will allow custom Wrapper to be used for your Button component. And we pass every inherited props into your Wrapper so that your route props will be received in your Link component.
You can then use it like so
<Button basic small as={Link} route="/register/location">
Sign Up
</Button>
This uses the ES6 spread operator syntax. Basically you render your Button component as a Link component, and any inherited props will be passed to the Link component, hence route props is passed into Link component.
This follow the API Design Approach similar to Material-ui's spead approach. This will allow your component to be more flexible as well.

How to trigger validation in formik after rendering?

I have one question with formik. Basically, I will have a table which list all the Id of the forms which have errors. When user click on the Id of a form, it will show up the form itself. The requirement is the errors should be showing also when the form is rendered. Does anyone know how to do that with Formik ? Also, if user edit the field the field validation should works as normal.
I put the codesandbox link here. https://codesandbox.io/s/pensive-brattain-yyls2. Basically I want that when the form show up I should see the errors, not just when user move away from the field or changing it. Thank you.
import { Formik, Field, Form } from "formik";
import { TextField } from "formik-material-ui";
class Post0 extends React.Component {
validateEmptyName(value) {
if (!value) {
return "Invalid Name";
}
}
render() {
return (
<div>
<Formik
initialValues={{
email: "",
animal: ""
}}
onSubmit={values => {
this.props.nextStep(values);
}}
render={({ values, isSubmitting }) => (
<Form>
<Field
name="email"
type="email"
value={values.email}
component={TextField}
variant="outlined"
validate={this.validateEmptyName}
/>
<Field
name="animal"
value={values.animal}
component={TextField}
variant="outlined"
/>
<button type="submit">Submit</button>
</Form>
)}
/>
</div>
);
}
}
I made a basic demo version using a custom input component. Formik has no built-in options to do this so unfortunately you need to create your own field components to integrate with Formik's props and bypass the logic that won't show validations if the form's not touched.
const Input = ({ field, form }) => {
useEffect(() => {
form.validateForm();
}, []);
return (
<div>
<input
style={{
border: form.errors[field.name] ? "1px solid red" : "1px solid #ccc"
}}
name={field.name}
value={field.value}
onChange={field.onChange}
/>
{form.errors[field.name] && (
<span style={{ color: "red" }}>{form.errors[field.name]}</span>
)}
</div>
);
};
And then pass this as the component prop on your <Field/>.
Formik does provide an isInitialValid prop which you could set to false on the main Formik component, but again the library TextFields you're using won't display anything without the touched prop.
2021 update:
Use validateOnMount prop:
https://formik.org/docs/api/formik#validateonmount-boolean
validateOnMount works if you also add initialTouched, but it has limitation (...or better say bug) when it shows validation issues after submit which doesn't lead to different view or component.
I have found pretty elegant workaround which works as expected.
const formikRef = React.useRef(null);
React.useEffect(() => formikRef.current.validateForm(), []);
return (
<Formik
innerRef={formikRef}
initialValues={props.customer}
initialTouched={mapObject(props.customer, true)}
onSubmit={values => {
.
.
.
you can use isInitialValid or initialErrors to valid initial values.
check their official docs here.
I accomplished this using Yup's validateAtSync function while populating the initial values of my form from the querystring.
function generateInitialValues(tabs: TabType[], router: any, validationSchema: any) {
const initialValues: { [key: string]: number | string } = {};
_.forEach(tabs, (tab: TabType) => {
_.forEach(tab.formFields, (f: FormField) => {
let isFieldValid;
try {
// https://github.com/jquense/yup#mixedvalidatesyncatpath-string-value-any-options-object-any
console.log('validation schema validateAt: ', validationSchema.validateSyncAt(f.id, router.query[f.id]));
isFieldValid = validationSchema.validateSyncAt(f.id, router.query[f.id]);
} catch (e) {
// do nothing on purpose to stop exception from being thrown
// TODO: Consider doing something here, such as recording a metric
}
initialValues[f.id] = isFieldValid ? router.query[f.id] : f.defaultValue;
})
});
return initialValues;
}

Content in reactstrap modal continues to exist after closing using enzyme/jest

I'm trying to do some testing with enzyme and jest in react, and things work fine when I open a modal e.g. input fields in the modal aren't there and the modal state is false (as intended) when I try to find them using
expect(wrapper.find("input")).toHaveLength(0);
and do exist after I've opened the modal using
const edit = wrapper.find("Button.update-button");
edit.simulate("click");
expect(wrapper.find("input")).toHaveLength(2);
which all works (including the modal state turning to true after it opens) as intended. But when I close the modal, the state gets toggled off correctly, but the modal content (e.g. the input boxes and buttons in the modal) still exist when I try:
expect(wrapper.find("input")).toHaveLength(0);
I still somehow have 2 input fields that shouldn't be there as the modal is closed.
Here is my code for the component I am trying to test if that helps:
/*
Artefact Component displays just UI for the Artefact itself and it's information.
*/
import React, { Component } from "react";
import DeleteArtefact from "../DeleteArtefact";
import UpdateArtefact from "../UpdateArtefact";
import {
Card,
CardImg,
CardTitle,
CardBody,
ButtonGroup,
Button,
CardFooter
} from "reactstrap";
class Artefact extends Component {
// Initialise State
state = {
updatemodal: false,
deletemodal: false
};
// Toggle function for toggling modal open/close
toggleUpdate = () => {
this.setState({
updatemodal: !this.state.updatemodal
});
};
toggleDelete = () => {
this.setState({
deletemodal: !this.state.deletemodal
});
};
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.toggleUpdate();
};
render() {
const {
artefact,
onChange,
onUpdateClick,
editUpdate,
onDeleteClick
} = this.props;
return (
<Card>
<CardImg
src={artefact.img}
alt={`Image for Artefact ${artefact.name}`}
/>
<CardBody>
<CardTitle>
<h6>{artefact.name}</h6>
</CardTitle>
</CardBody>
<CardFooter>
<ButtonGroup>
<Button
className="update-button"
color="dark"
onClick={this.prepareUpdateState}
>
Edit
</Button>
<Button
className="delete-button"
color="dark"
onClick={this.toggleDelete}
>
Delete
</Button>
</ButtonGroup>
<UpdateArtefact
artefact={artefact}
onChange={onChange}
onUpdateClick={onUpdateClick}
editUpdate={editUpdate}
toggle={this.toggleUpdate}
modal={this.state.updatemodal}
/>
<DeleteArtefact
_id={artefact._id}
onDeleteClick={onDeleteClick}
toggle={this.toggleDelete}
modal={this.state.deletemodal}
/>
</CardFooter>
</Card>
);
}
}
export default Artefact;
And here is the UpdateArtefact Component that has the modal I'm trying to test:
/*
UpdateArtefact Component is a child Component of ArtefactGallery and
creates a new Artefact by using functions onChange() and updateClick()
and editUpdate() which are passed as props from ArtefactGallery and
passes state back up and makes api calls using axios.
*/
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input
} from "reactstrap";
class UpdateArtefact extends Component {
// Passes state up to ArtefactGallery component and updates the artefact.
onSubmit = e => {
e.preventDefault();
this.props.onUpdateClick(this.props.artefact._id);
this.props.toggle();
};
// Sets state in ArtefactGallery to the initial values of the artefact
// to prepare for any edits to be made in the case that some fields have
// no change, so that there are no null fields.
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.props.toggle();
};
render() {
const { artefact } = this.props;
return (
<div style={{ marginLeft: "1rem" }}>
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
<ModalHeader toggle={this.props.toggle}>
Edit Artefact
</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label>Artefact</Label>
<Input
type="text"
name="name"
id="artefactName"
defaultValue={artefact.name}
onChange={this.props.onChange}
/>
<Label>Image</Label>
<Input
type="text"
name="img"
id="artefactImg"
defaultValue={artefact.img}
onChange={this.props.onChange}
/>
<Button
className="modal-submit-button"
color="dark"
style={{ marginTop: "2rem" }}
block
>
Submit
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
export default UpdateArtefact;
So basically I just want to know what the reason if for why the modal content is still being picked up by enzyme and how to fix this. I've tried searching all over but couldn't find an answer so I'm guessing there's something obvious that I'm missing.
See, your components does not use conditional rendering like
{someFlag && <SomeElement>}
but just pass down isOpen prop:
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
so probably Modal just hides its props.children and input is kept.
As a workaround you may validate against ModalComponentYouHaveRendered.props().isOpen instead of checking amount of inputs
You can try using:
wrapper.update()
after closing the modal.
In this way, the wrapper should get updated.

In React ES6, why does the input field lose focus after typing a character?

In my component below, the input field loses focus after typing a character. While using Chrome's Inspector, it looks like the whole form is being re-rendered instead of just the value attribute of the input field when typing.
I get no errors from either eslint nor Chrome Inspector.
Submitting the form itself works as does the actual input field when it is located either in the render's return or while being imported as a separate component but not in how I have it coded below.
Why is this so?
Main Page Component
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionPost from '../redux/action/actionPost';
import InputText from './form/InputText';
import InputSubmit from './form/InputSubmit';
class _PostSingle extends Component {
constructor(props, context) {
super(props, context);
this.state = {
post: {
title: '',
},
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(event) {
this.setState({
post: {
title: event.target.value,
},
});
}
onSubmit(event) {
event.preventDefault();
this.props.actions.postCreate(this.state.post);
this.setState({
post: {
title: '',
},
});
}
render() {
const onChange = this.onChange;
const onSubmit = this.onSubmit;
const valueTitle = this.state.post.title;
const FormPostSingle = () => (
<form onSubmit={onSubmit}>
<InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
<InputSubmit name="Save" />
</form>
);
return (
<main id="main" role="main">
<div className="container-fluid">
<FormPostSingle />
</div>
</main>
);
}
}
_PostSingle.propTypes = {
actions: PropTypes.objectOf(PropTypes.func).isRequired,
};
function mapStateToProps(state) {
return {
posts: state.posts,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actionPost, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(_PostSingle);
Text Input Component
import React, { PropTypes } from 'react';
const InputText = ({ name, label, placeholder, onChange, value, error }) => {
const fieldClass = 'form-control input-lg';
let wrapperClass = 'form-group';
if (error && error.length > 0) {
wrapperClass += ' has-error';
}
return (
<div className={wrapperClass}>
<label htmlFor={name} className="sr-only">{label}</label>
<input type="text" id={name} name={name} placeholder={placeholder} onChange={onChange} value={value} className={fieldClass} />
{error &&
<div className="alert alert-danger">{error}</div>
}
</div>
);
};
InputText.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
error: PropTypes.string,
};
InputText.defaultProps = {
value: null,
error: null,
};
export default InputText;
Submit Button Component
import React, { PropTypes } from 'react';
const InputSubmit = ({ name }) => {
const fieldClass = 'btn btn-primary btn-lg';
return (
<input type="submit" value={name} className={fieldClass} />
);
};
InputSubmit.propTypes = {
name: PropTypes.string,
};
InputSubmit.defaultProps = {
name: 'Submit',
};
export default InputSubmit;
it is because you are rendering the form in a function inside render().
Every time your state/prop change, the function returns a new form. it caused you to lose focus.
Try putting what's inside the function into your render directly.
<main id="main" role="main">
<div className="container-fluid">
<FormPostSingle />
</div>
</main>
===>
<main id="main" role="main">
<div className="container-fluid">
<form onSubmit={onSubmit}>
<InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
<InputSubmit name="Save" />
</form>
</div>
</main>
This happened to me although I had keys set!
Here's why:
I was using a key from a text field. Inside the same block; I had an input field to update the value of the same text field. Now, since component keys are changing, react re-renders the UI. Hence loosing focus.
What to take from this:
Don't use keys which are constantly changing.
What's happening is this:
When your onChange event fires, the callback calls setState with the new title value, which gets passed to your text field as a prop. At that point, React renders a new component, which is why you lose focus.
My first suggestion would be to provide your components keys, particularly the form and the input itself. Keys allow React to retain the identity of components through renders.
Edit:
See this documentation on keys: https://reactjs.org/docs/lists-and-keys.html#keys
Example:
<TextField
key="password" // <= this is the solution to prevent re-render
label="eMail"
value={email}
variant="outlined"
onChange={(e) => setEmail(e.target.value)}
/>
Had the same issue and solved it in a quick & easy manner: just calling the component with {compName()} instead of <compName />
For instance, if we had:
const foo = ({param1}) => {
// do your stuff
return (
<input type='text' onChange={onChange} value={value} />
);
};
const main = () => (
<foo param1={true} />
);
Then, we just need to change the way we call the foo() component:
const main = () => (
{foo({param1: true})}
);
By adding
autoFocus="autoFocus"
in the input worked for me
<input
type="text"
autoFocus="autoFocus"
value = {searchString}
onChange = {handleChange}
/>
You have to use a unique key for the input component.
<input key="random1" type="text" name="displayName" />
The key="random1" cannot be randomly generated.
For example,
<div key={uuid()} className='scp-ren-row'>
uuid() will generate a new set of string for each rerender. This will cause the input to lose focus.
If the elements are generated within a .map() function, use the index to be part of the key.
{rens.map((ren,i)=>{
return(
<div key={`ren${i+1}`} className='scp-ren-row'>
{ren}{i}
</div>)
}
This will solve the issue.
I also had this problem, my problem was related to using another component to wrap the textarea.
// example with this problem
import React from 'react'
const InputMulti = (props) => {
const Label = ({ label, children }) => (
<div>
<label>{label}</label>
{ children }
</div>
)
return (
<Label label={props.label}>
<textarea
value={props.value}
onChange={e => props.onChange(e.target.value)}
/>
</Label>
)
}
export default InputMulti
when the state changed, react would render the InputMulti component which would redefine the Label component every time, meaning the output would be structurally the same, but because of JS, the function would be considered !=.
My solution was to move the Label component outside of the InputMulti component so that it would be static.
// fixed example
import React from 'react'
const Label = ({ label, children }) => (
<div>
<label>{label}</label>
{ children }
</div>
)
const InputMulti = (props) => {
return (
<Label label={props.label}>
<textarea
value={props.value}
onChange={e => props.onChange(e.target.value)}
/>
</Label>
)
}
export default InputMulti
I've noticed that people often place locally used components inside the component that wants to use it. Usually to take advantage of function scope and gain access to the parent component props.
const ParentComp = ({ children, scopedValue }) => {
const ScopedComp = () => (<div>{ scopedValue }</div>)
return <ScopedComp />
}
I never really thought of why that would be needed, since you could just prop-drill the props to the internal function and externalise it from the parent comp.
This problem is a perfect example of why you should always externalise your components from each other, even if they are used in one module. Plus you can always use smart folder structures to keep things close by.
src/
components/
ParentComp/
ParentComp.js
components/
ScopedComp.js
I had a similar issue when using styled-components inside a functional component. The custom input element was losing focus every time I typed a character.
After much searching and experimenting with the code, I found that the styled-components inside the functional component was making the input field re-render every time I typed a character as the template literal syntax made the form a function although it looks like an expression inside Devtools. The comment from #HenryMueller was instrumental in making me think in the right direction.
I moved the styled components outside my functional component, and everything now works fine.
import React, { useState } from "react";
import styled from "styled-components";
const StyledDiv = styled.div`
margin: 0 auto;
padding-left: 15px;
padding-right: 15px;
width: 100%;
`;
const StyledForm = styled.form`
margin: 20px 0 10px;
`;
const FormInput = styled.input`
outline: none;
border: 0;
padding: 0 0 15px 0;
width: 100%;
height: 50px;
font-family: inherit;
font-size: 1.5rem;
font-weight: 300;
color: #fff;
background: transparent;
-webkit-font-smoothing: antialiased;
`;
const MyForm = () => {
const [value, setValue] = useState<string>("");
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if(value.trim() === '') {
return;
}
localStorage.setItem(new Date().getTime().toString(), JSON.stringify(value));
setValue('');
}
return (
<StyledDiv>
<StyledForm onSubmit={handleSubmit}>
<FormInput type="text"
id="inputText"
name="inputText"
placeholder="What Do You Want To Do Next?"
value={value}
onChange={handleChange}/>
</StyledForm>
</StyledDiv>
)
}
export default MyForm;
The best way to use styled-components in cases like this would be to save them in separate files and import them.
My issue was it was rerendering in a stateless component in the same file. So once I got rid of that unecessary stateless component and just put the code in directly, I didn't have unecessary rerenders
render(){
const NewSocialPost = () =>
<div className='new-post'>
<input
onChange={(e) => this.setState({ newSocialPost: e.target.value })}
value={this.state.newSocialPost}/>
<button onClick={() => this._handleNewSocialPost()}>Submit</button>
</div>
return (
<div id='social-post-page'>
<div className='post-column'>
<div className='posts'>
<Stuff />
</div>
<NewSocialPost />
</div>
<MoreStuff />
</div>
I'm new to React, and have been running into this issue.
Here's what I did to solve:
First move all of your components into your components folder and then import them where you want to use them
Make sure all of your form elements get a name and id property
Make sure all components as you walk up the tree get a unique key
Someone smarter than me can probably tell us why we can skip step one and keep everything inline so to speak, but this just helped me organize the code.
I think the real issue is React is rerendering everything (as already stated) and sometimes that rerender is happening on a parent component that doesn't have a key but needs one.
My problem was with ExpansionPanel components wrapping my custom components for form inputs. The panels needed key as well!
Hope this helps someone else out there, this was driving me crazy!
Basically create a ref and assign it to the input element
const inputRef = useRef(null); // Javascript
const inputRef = useRef<HTMLInputElement>(null); // Typescript
// In your Input Element use ref and add autofocus
<input ref={inputRef} autoFocus={inputRef.current === document.activeElement} {...restProps} />
This will keep the input element in focus when typing.
The problem is with dynamic render() caused by useState() function so you can do this for example.
in this code you should use onChange() to get just the new updated data and onMouseLeave() to handle the update but with condition that data is changed to get better performance
example child
export default function Child(){
const [dataC,setDataC]=useState()
return(<Grid>
<TextField
.
.
onChange={(r)=> setDataC(r.target.value) }
onMouseLeave={(e)=> {
if(dataC!=props.data) { // to avoid call handleupdate each time you leave the textfield
props.handlechange(e.target.value) // update partent.data
}
}
/>
</Grid>)
}
exmple parent
export default function Parent(){
const [data,setData]=useState()
return(
<Grid>
<Child handlechange={handlechanges} data={data}/>
</Grid>)
}
I was facing the same issue, as soon as I type any character, it was losing focus. adding autoFocus props helped me to resolve this issue.
TypeScript Code Snippet
Solution -
Add a unique key to the input element because it helps React to identify which item has changed(Reconciliation). Ensure that your key should not change, it has to be constant as well as unique.
If you are defining a styled component inside a react component. If your input element is inside that styled component then define that styled component outside the input's component. Otherwise, on each state change of the main component, it will re-render your styled component and input as well and it will lose focus.
import React, { useState } from "react";
import styled from "styled-components";
const Container = styled.div`
padding: 1rem 0.5rem;
border: 1px solid #000;
`;
function ExampleComponent() {
// Container styled component should not be inside this ExampleComponent
const [userName, setUserName] = useState("");
const handleInputChange = event => {
setUserName(event.target.value);
};
return (
<React.Fragment>
<Container> {/* Styled component */}
<input
key="user_name_key" // Unique and constant key
type="text"
value={userName}
onChange={handleInputChange}
/>
</Container>
</React.Fragment>
);
}
export default ExampleComponent;
In my case, I had this on a child,
//in fact is a constant
const RenderOnDelete=()=>(
<> .
.
<InputText/>
.
.
</>
)
//is a function that return a constant
const RenderOnRadioSelected=()=>{
switch (selectedRadio) {
case RADIO_VAL_EXIST:
return <RenderOnExist/>
case RADIO_VAL_NEW:
return <RenderOnNew/>
case RADIO_VAL_DELETE:
return <RenderOnDelete/>
default:
return <div>Error</div>
}
}
and this in the parent
return(
<>
.
<RenderOnRadioSelected/>
.
</>
)
Y solved it by not calling a component but a function() or a constant, depending on the case.
.
.
.
//in fact is a constant
const RenderOnDelete=(
<> .
.
<InputText/>
.
.
</>
)
//is a function that return a constant
const RenderOnRadioSelected=()=>{
switch (selectedRadio) {
case RADIO_VAL_EXIST:
return {RenderOnExist}
case RADIO_VAL_NEW:
return {RenderOnNew}
case RADIO_VAL_DELETE:
return {RenderOnDelete}//Calling the constant
default:
return <div>Error</div>
}
}
and this in the parent
return(
<>
.
{RenderOnRadioSelected()}//Calling the function but not as a component
.
</>
)
Adding yet another answer: This happened to me when returning a higher order component inside another component. Eg instead of:
/* A function that makes a higher order component */
const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
const { awesomeThing, ...passThroughProps } = props;
return (
<strong>Look at: {awesomeThing}!</strong>
<Component {...passThroughProps} />
);
}
return AwesomeComponent;
}
/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {
const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> =
makeMyAwesomeHocComponent(TextInput);
return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}
Move the call to create the higher order component out of the thing you're rendering.
const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
const { awesomeThing, ...passThroughProps } = props;
return (
<strong>Look at: {awesomeThing}!</strong>
<Component {...passThroughProps} />
);
}
return AwesomeComponent;
}
/* We moved this declaration */
const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> =
makeMyAwesomeHocComponent(TextInput);
/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {
return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}
Solution for this problem is to use useCallback It is used to memoize functions which means it caches the return value of a function given a set of input parameters.
const InputForm = useCallback(({ label, lablevalue, placeholder, type, value,setValue }) => {
return (
<input
key={label}
type={type}
value={value}
onChange={(e) => setIpValue(e.target.value)}
placeholder={placeholder}
/>
);
},[]);
Hope it will solve your problem
If you happen to be developing atomic components for your app's design system, you may run into this issue.
Consider the following Input component:
export const Input = forwardRef(function Input(
props: InputProps,
ref: ForwardedRef<HTMLInputElement>,
) {
const InputElement = () => (
<input ref={ref} {...props} />
);
if (props.icon) {
return (
<span className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<label htmlFor={props.id} className="p-1 cursor-pointer">
{icon}
</label>
</span>
<InputElement />
</span>
);
} else {
return <InputElement />;
}
});
It might seem like a simple optimization at first to reuse your input element across both branches of your conditional render. However, anytime the parent of this component re-renders, this component re-renders, and when react sees <InputElement /> in the tree, it's going to render a new <input> element too, and thus, the existing one will lose focus.
Your options are
memoize the component using useMemo
duplicate some code and define the <input> element in both branches of the conditional render. in this case, it's okay since the <input> element is relatively simple. more complex components may need option 1
so your code then becomes:
export const Input = forwardRef(function Input(
props: InputProps,
ref: ForwardedRef<HTMLInputElement>,
) {
if (props.icon) {
return (
<span className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<label htmlFor={props.id} className="p-1 cursor-pointer">
{icon}
</label>
</span>
<input ref={ref} {...props} />
</span>
);
} else {
return <input ref={ref} {...props} />;
}
});
I did the following steps:
Move dynamic component outside a function
Wrap with useMemo function
const getComponent = (step) =>
dynamic(() => import(`#/components/Forms/Register/Step-${step}`), {
ssr: false,
});
And call this function inside the component by wrapping useMemo:
const CurrentStep = useMemo(() => getComponent(currentStep), currentStep]);
I'm very late but I have been tracking down this issue for days now and finally fixed it. I hope it helps someone.
I'm using Material-ui's Dialog component, and I wanted the dialog to show when a menu item was clicked. Something like so:
import React, { useState } from "react";
import {
Menu,
MenuItem,
Dialog,
DialogContent,
TextField,
} from "#mui/material";
const MyMenu = () => {
const [open, setOpen] = useState(false);
return (
<Menu>
<MenuItem>option 1</MenuItem>
<MenuItem onClick={() => setOpen(!open)}>
option 2
<Dialog open={open}>
<DialogContent>
<TextField />
</DialogContent>
</Dialog>
</MenuItem>
</Menu>
);
};
I was having issues with the TextField losing focus, but only when hitting the a, s, d, c and v keys. If I hit any one of those keys, it would not type anything in the textfield and just lose focus. My assumption upon fixing the issue was that some of the menu options contained those characters, and it would try to switch focus to one of those options.
The solution I found was to move the dialog outside of the Menu component:
const MyMenu = () => {
const [open, setOpen] = useState(false);
return (
<>
<Menu>
<MenuItem>option 1</MenuItem>
<MenuItem onClick={() => setOpen(!open)}>
option 2
</MenuItem>
</Menu>
<Dialog open={open}>
<DialogContent>
<TextField />
</DialogContent>
</Dialog>
</>
);
};
I am unable to find anyone with my specific issue online, and this was the post that came up at the top in my searches so I wanted to leave this here. Cheers
I am not authorised to comment then it must be an answer. I had similar issue and Answer from Alex Yan was corect.
Namely I had that function
const DisplaySearchArea =()=>{return (arrayOfSearchFieldNames.map((element, index)=>{return(<div key ={index} className = {inputFieldStyle}><input placeholder= {arrayOfPlaceholders[index]} type="text" className='border-0'
value={this.state[element]}
onChange={e => {this.setState({ [element]: e.target.value }); console.log(e.target)}}
onMouseEnter={e=>e.target.focus()}/></div>)}))}
that behaves OK with FF and not with Chrome when rendered as <DisplaySearchArea />
When render as {...} it's OK with both. That is not so 'beaty' looking code but working, I have already been told to have tendency to overuse lambdas.
Thanks, Alex. This way I solved my issue:
constructor(props, context) {
...
this.FormPostSingle = this.FormPostSingle.bind(this);
}
FormPostSingle() {
const onChange = this.onChange;
const onSubmit = this.onSubmit;
const valueTitle = this.state.post.title;
return (
<form onSubmit={onSubmit}>
<InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
<InputSubmit name="Save" />
</form> );
}
render() {
let FormPostSingle = this.FormPostSingle
return...
}
set the correct id, make sure no other component has same id, set it unique, and it should not change on state update, most common mistake is updating the id with changed value on state update
I had this issue, it was being cause by react-bootstrap/Container, once I got rid of it, included a unique key for every form element, everything worked fine.
For the ones on React Native facing the issue where the text input goes out of focus after typing in single character.
try to pass your onChangeText to your TextInput component.
eg:
const [value, setValue] = useState("")
const onChangeText = (text) => {
setValue(text)
}
return <TextInput value={value} onChangeText={onChangeText} />
This is a great question, and I had the same problem which was 3 parts.
RandomGenerated keys.
Wrong event type.
wrong react JSX attribute.
Keys: when you use random keys each rerender causes react to lose focus (key={Math.random()*36.4621596072}).
EventTypes: onChange cause a rerender with each key stroke, but this can also cause problems. onBlur is better because it updates after you click outside the input. An input, unless you want to "bind" it to something on the screen (visual builders), should use the onBlur event.
Attributes: JSX is not HTML and has it's own attributes (className,...).
Instead of using value, it is better to use defaultValue={foo} in an input.
once I changes these 3 things it worked great. Example below.
Parent:
const [near, setNear] = useState( "" );
const [location, setLocation] = useState( "" );
<ExperienceFormWhere
slug={slug}
questionWhere={question_where}
setLocation={handleChangeSetLocation}
locationState={location}
setNear={setNear}
nearState={near}
key={36.4621596072}/>
Child:
<input
defaultValue={locationState}
className={slug+"_question_where_select search_a_location"}
onBlur={event => setLocation(event.target.value)}/>
I did it with a useRef on input and useEffect
For me this was happening inside Material UI Tabs. I had a search input filter which filtered the table records below it. The search input and table were inside the Tab and whenever a character was typed the input would lose focus (for the obvious reason of re render, the whole stuff inside a tab).
I used the useRef hook for input field ref and then inside my useEffect I triggered the input's focuswhenever the datalist changed. See the code below
const searchInput = useRef();
useEffect(() => {
searchInput.current.focus();
}, [successfulorderReport]);
If working with multiple fields – and they have to be added and removed dynamically for whatever reason – you can use autofocus. You have to keep track of the focus yourself, though. More or less like this:
focusedElement = document.activeElement.id;
[…]
const id = 'dynamicField123'; // dynamically created.
<Input id={id} key={id} {...(focusedElement === id ? { autoFocus: true } : {})} />
This issue got me for a second. Since I was using Material UI, I tried to customize one of the wrapper components of my form using the styled() API from material UI. The issue was caused due to defining the DOM customization function inside my render function body. When I removed it from the function body, it worked like a charm. So my inspection is, whenever I updated the state, it obviously tried to refresh the DOM tree and redeclare the styled() function which is inside the render body, which gave us a whole new reference to the DOM element for that wrapper, resulting in a loss of focus on that element. This is just my speculation, please enlighten me if I am wrong.
So removing the styled() implementation away from the render function body solved the issue for me.
This is silly, but... are you (reader, not OP) setting disabled={true} ever?
This is a silly contribution, but I had a problem very much like the one this page is talking about. I had a <textarea> element inside a component that would lose focus when a debounce function concluded.
Well, I realized I was on the wrong track. I was setting the <textarea> to disabled={true} whenever an auto-save function was firing because I didn't want to let the user edit the input while their work was being saved.
When a <textarea> is set to be disabled it will lose focus no matter what trick you try shared here.
I realized there was zero harm in letting the user continue to edit their input while the save was occurring, so I removed it.
Just in case anyone else is doing this same thing, well, that might be your problem. 😅 Even a senior engineer with 5 years of React experience can do things that dumb.

Resources