Improve textfield render performance - javascript

I'm using React 17 and MUI 5. I found a problem in my project when I have a component with a huge form with multiple <Textfields>.
handleChange = (event) => {
this.setState({ value: event.target.value });
};
...
<TextField
value={this.state.value}
onChange={this.handleChange}
label="Textfield"
/>
Everytime I type a char in my textfield the setState is triggered and my render() function is called everytime. So on my huge form I have a lag when I type.
So I tried the solution to use onBlur event:
<TextField
onBlur={this.handleChange}
label="Textfield"
/>
It works well if I'm on "Add" mode because the setState is triggered only when I leave the textfield. But I have a problem when I'm on "Edit" mode. When I'm on "Edit" mode I get the current value and I put it inside my textfield. And with onBlur event I can't use the textfield value prop because there is a conflict and I can't write anymore in the textfield.
So I found another solution. It's to use ref. But I have the same problem like with onBlur event. I can't use value prop. So I did this :
<TextField
inputRef={(el) => {
this.textRef = el;
if (el && this.state.mode === "edit") {
this.textRef.value = this.state.node.value;
}
}}
label="Textfield"
/>
So when I'm on edit mode I init the textfield with the current value (this.state.node is a node of my TreeView and each node have a edit button). This code works. But I have the feeling it's not the good way to do that. This if mode == edit is weird.
Do you have a better way ?

You could examine uncontrolled inputs, it could fit in your case
An uncontrolled form doesn’t have state, so every time you type, your component doesn’t re-render, increasing overall performance.
There is an explanation: https://hackernoon.com/you-might-not-need-controlled-components-fy1g360o

I doubt the "huge form" is actually the problem. React only updates the DOM when there are changes, so even though the component is rerendered on every character typed, only the one input field that changes should get flushed to the DOM.
Check if you're doing expensive calculations in your render() function. In that case, memoize these.
Another take on fixing this would be to use uncontrolled components as mentioned in the other answer.
Also consider if you can split up your component into multiple, smaller components so the problem is minimized.
The following "benchmark" code is written with functional components but is using MUI v5 and React 17 and runs with 100 TextFields without any noticable lag when entering text, at least on my (relatively beefy) desktop PC.
Using <input> from HTML even allowed me to get this working with 1000 elements.
const inputFieldKeys = Array.from({
length: 100
}).map((e, i) => "field_" + i.toString());
const App = (props) => {
const [inputState, setInputState] = React.useState({});
// this runs fine even without the `useCallback` optimization, at least for me
const handleChange = React.useCallback((e) => {
setInputState(oldState => ({ ...oldState,
[e.target.name]: e.target.value
}));
}, [setInputState]);
return ( <div>
{inputFieldKeys.map(key=>(
<MaterialUI.TextField name={key} key={key} value={inputState[key] || ""} onChange={handleChange}
label={key}/>
))}
</div>
);
};
ReactDOM.render( < App / > , document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/#mui/material#5/umd/material-ui.production.min.js"></script>
<div id="root"></div>
Note for MUI performance:
MUI v5 uses the sx prop for inline styling. This can lead to bad performance in cases with many components when used without care. mui docs

Related

React: Warning, a component is changing an uncontrolled input to be controlled

I'm using Google maps address autocomplete in my React app. It works by hooking into an input element, watching for changes, and providing a dropdown location select.
Relevant code:
<InputGroup hasValidation className="mb-3">
<FormControl id="autocomplete"/>
</InputGroup>
useEffect(() => {
// only specify the fields we need, to minimize billing
const options = {"fields": ["address_components"]}
const autocompleteElement = document.getElementById("autocomplete")
if(autocompleteElement) {
autocomplete.current = new google.maps.places.Autocomplete(autocompleteElement, options);
const listener = autocomplete.current.addListener("place_changed", placeSelected);
return function cleanUp() {
google.maps.event.clearInstanceListeners(listener);
}
} else {
// nothing to do yet
return;
}
});
However, I'm getting a warning in the browser console:
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component
Seems obvious enough- the autocomplete functionality is changing the input itself as opposed to using react state to make a controlled component. However that's the way I want it. Is there a way I can silence this error? I've tried adding an empty defaultValue and an empty onChange function, but still got the error. Thanks in advance!
(There were a few questions with the same issue, but nothing about deliberately disabling the warning)
I have faced such warnings on a couple of projects here is one of the causes and solution.
const [value, setValue] = useState("");
<input value={value} onChange={inputHandler} />
From the code above notice that the state initial value is "", check yours. Its possible you are using null or empty value.
You need to change it to empty string and that warning will disappear.
Let me know if its helpful.
We cleared this error by simply adding default values to our form inputs:
<p style={{ fontWeight: 'bold' }}>Devices: </p>{' '}
<Select
isMulti
value={mapValuesToSelect(devices) || []}
// this one rigth here ^^^^^
onChange={(e) => editDevicesMultiSelect(e)}
or simple input:
<Input
type="text"
value={code || ''}
// ^^^^^
P.S. we also have a handleSelectOnClose function:
<Button onClick={handleUnselect}>
CLOSE
</Button>
const handleUnselect = () => {
dispatch(setCurrentDesk(undefined));
};
Use a regular uncontrolled html input instead of one of the controlled react-bootstrap inputs.
You can use a ref to refer to the input.
<InputGroup hasValidation className="mb-3">
<input
defaultValue="Belgium"
type="text"
ref={this.autocomplete} />
</InputGroup>
More info on uncontrolled inputs and ref usage here:
https://reactjs.org/docs/uncontrolled-components.html
You can try using a third party custom package like:
React Places autocomplete.
This package provides an option to use controlled input, as well as other props to customise styling and other methods
const [value, setValue] = useState(null);
<GooglePlacesAutocomplete
selectProps={{
value,
onChange: setValue,
}}
/>
You need to give an initial value and an onChange method. I'd probably use a hook to make this easier to manage. You could then use the useEffect() hook to call the API when the value of inputText changes.
const [inputText, setInputText] = useState('');
useEffect(() => {
if (inputText.length > 0) {
... call the api
}
},[inputText])
<InputGroup hasValidation className="mb-3">
<FormControl id="autocomplete" value={inputText} onChange={(event) => {setInputText(event.target.value}}/>
</InputGroup>
as Blessing Ladejobi said its beacuse of
<input value={myValue} onChange={inputHandler} /> and myValue=null
solution is define a default value for myValue (like myValue="")

Input fields lose focus on each value change in a complex Formik form

I'm building an app that will need a complex form generation (from json/js object).
Here's an example of what I'm building: https://codesandbox.io/s/formik-form-wizard-test-vcj1t
My problem is the following:
all the input fields lose focus on every value change
The folder structure of the CodeSandbox project is the following (for easier code comprehension):
./src:
App.js - main file with all the form generation (generateWizardSteps is the main function that does all the generation).
formSetup.js - that's an object that defined form configuration. From objects like this I need to build dynamic forms. This object has the array of pages (these are the steps of the wizard), and each page has an array of fields (like input, select, checkbox etc). In its turn, each field has props property. props is what I pass to the React component as props.
formComponents.js - this file contains all the form field React components that I use for generating my forms.
decorateWithFormik.js - this file is just to make App.js a bit smaller. It's just the useFormik decorator.
The form is built using the formik library. Also, as I need a wizard-like form, I've found a nice library for it: formik-wizard-form.
I've looked through the stackoverflow questions on similar topics but couldn't find something that could fit my needs. Many questions/answers are about the problem with dynamic key props, but mine are static as far as I can tell (they all are taken from the initial formSetup object).
Another thing that I've noticed is that my form gets re-rendered on every value change, but I'm not sure if this is a problem at all.
Could you help me to figure out what the problem is, why does it happend and how to make my form fields not lose focus?
Solved the issue
I've removed all the possible component creation code and moved everything inside the component props of the Step component provided by the formik-wizard-form library.
Here's my working solution: https://codesandbox.io/s/formik-form-wizard-test-lz3x7 (hope this will help somebody)
Many thanks to everyone in the comments section!
Main insights:
Losing focus means that the component unmounts and remounts every time.
This unmounting/remounting behaviour on every change was caused by the creation of components inside the render function of another component. Consequently, on every re-render the input components were created anew.
My final working code:
const MyForm = props => {
return (
<FormikWizardProvider {...props}>
{renderProps => (
<Wizard {...renderProps}>
<StepsList>
{formSetup.pages.map(page => (
<Step
title={page.name}
key={page.name}
component={() =>
page.fields.map(field => {
if (
field.props &&
field.props.visibilityCheckbox &&
!props.values[field.props.visibilityCheckbox]
) {
return null;
}
const fieldProps = {
formik: props,
key: field.props.name,
...field.props
};
switch (field.type) {
case "input":
return <MyTextInput {...fieldProps} />;
case "radio":
return <RadioButtonGroup {...fieldProps} />;
case "checkbox":
return <MyCheckbox {...fieldProps} />;
case "select":
return <MySelect {...fieldProps} />;
default:
return null;
}
})
}
/>
))}
</StepsList>
<ButtonsList>
<PreviousButton />
<NextButton />
<SubmitButton />
</ButtonsList>
</Wizard>
)}
</FormikWizardProvider>
);
};
export default decorateWizardWithFormik(MyForm);

React + Redux: Changing input focus programatically

I have a react application with a 'yes/no' question interface. I'm using redux to manage state.
The app presents a series of input fields, which are added dynamically.
When a question is answered with a 'y' or 'n' keystroke, I want the next input in the series to get focus automatically -- enabling fast data-entry. This is proving surprisingly difficult!
My redux store contains the current question's index - I want this to translate into focus on that input.
/*Input Component*/
const quizQs = ({
questionArray = ["Q1", "Q2", "Q3", "Q4"]
currentQIndex, //From Store
changeQIndex, //Action
}) => {
const _handleKeyDown = (e) => {
if(e.key == 'y' || e.key == 'n'){
//Dispatches Action that increases current currentQIndex'
}
}
//_handleFocus()... for updating currentQIndex if an input is focused by the user
return (
{questionArray.map((q, index) => {
return(
<input
key={index}
onKeyDown={_handleKeyDown}
onFocus={_handleFocus}
type="text"
placeholder={q}
/>
)
})}
)
}
/*Main Component -- Connected to Store*/
class myQuiz extends React.Component {
constructor(props){
super(props);
}
render(){
return(
<div>
<quizQs
currentQIndex = {this.props.currentQIndex}
changeQIndex = {this.props.changeQIndex}
/>
</div>
)}
}
I have tried setting autoFocus = true, if the store's 'currentQIndex' matches the index of that particular question, in the 'quizQs' component. This method is able to focus the specified field when the page first renders, but the focus does not change when the 'currentQIndex' of the store changes.
As I have searched for an answer, React 'refs' + use of a callback would seem to be the way to go, (https://reactjs.org/docs/refs-and-the-dom.html#the-ref-callback-attribute), but I cannot figure out how to set up such 'focus' callback, that responds to changes in the Redux store.
In addition, in order to use Refs, the component must be set up as a class, not an arrow function. AFAIK, it is not good practice to have multiple classes in one file, and it does not seem appropriate to connect so many different components to a redux store.
I'd appreciate help.
Here is simple example of what you are trying to achieve: https://codesandbox.io/s/z64qw3nkzx
I simplified it a bit, but the point is there.
As .focus() is native method on the DOM element, you need a way of tracking those input elements. For that in React there is ref prop. It accepts a function with has one parameter which is the actual DOM element of the component.
You'll see that I put all of the DOM references into an array:
<input
ref={el => this.questionInputElements[index] = el}
key={index}
// all other props
/>
and on key up* find the next element in the array and focus it:
const nextInput = this.questionInputElements[index + 1];
if (nextInput) {
nextInput.focus();
}
* it needs to be on key up (rather than key down) as it would focus the next field before and print y/n in the next input. Try it for fun :)

Testing React components - mount vs shallow and simulating events

I have the following React components that I'm trying to test by simulating entry into the input field with id=componentCount. I started using React for the first time less than one week ago, so this framework is very new to me and any help would be appreciated.
I'm also using Semantic UI React for the CSS framework, hence the Form.Group and Form.Input tags
export class SoftwareForm extends React.Component {
constructor(props) {
super(props);
this.state = {
componentCount: 0
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<Form id='softwareForm'>
<Form.Group>
<Form.Input label='Name' placeholder='Component Name' id='componentName' type='text'/>
<Form.Input label='Description' placeholder='Component Description' id='componentDescription' type='text'/>
<Form.Input name='componentCount'
label='Count'
placeholder='Count'
id='componentCount'
type='number'
min='0'
value={this.state.componentCount}
onChange={this.handleInputChange}/>
</Form.Group>
</Form>
);
}
}
Test script
describe('<SoftwareForm />', () => {
it('Count field accepts text input', () => {
const softwareFormComponent = mount(<SoftwareForm />);
const countField = softwareFormComponent.find('#componentCount');
countField.simulate('change', {target: {value: 3}});
expect(softwareFormComponent.state('componentCount')).toBe(3);
});
});
The above test script uses mount for full rendering, but I get the following error, "Method “simulate” is only meant to be run on a single node. 4 found instead."
If I use a shallow mount, the test output is the following
Expected value to be (using ===):
3
Received:
0
This tells me that the simulate method is not working as expected.
Several questions:
Can a shallow render be used in this scenario since I can use the find method to search for the element with the 'componentCount' id attribute or is a full render with a mount necessary since I'm trying to manipulate an input element which is a child of the Form.Group?
Regardless of the answer the question 1, when mount is used, the wrapper that is returned contains 4 nodes - what are these 4 nodes and which one am I supposed to call simulate on if a full render is in fact needed in this case?
I'm struggling due to not having a clear understanding of the structure of the wrapper object that is returned by either shallow or mount.
The idea of the last two statements in the test script is to simulate the change to the value of the number in the input field, have the onChange event handler trigger an update of the componentCount state value and then the perform a comparison using the assert statement. Is this the correct approach?
Trying to answering your questions:
You could use mount, which create test on a full DOM rendering and it is ideal for use cases where you have components that may interact with DOM APIs, or may require the full lifecycle in order to fully test the component.
You can see that find returns using .debug().
After a few more attempts, I was able to get the test to work.
A shallow mount ended up being sufficient in this case even though I was interacting with input DOM element.
The simulate method used in the test script was changed to the following,
"countField.simulate('change',{
target: { name: "componentCount", value: 3 } });

react-codemirror beforeChange event

I am using react-codemirror node module as follows:
<CodeMirror
className={className}
value={this.state.code}
onBeforeChange={this.onBeforeChange}
onChange={this.onChange}
options={options}
/>
The change event works fine, but I can't seem to hook up with the beforeChange event. Anyone know what I am doing wrong?
I have declared handlers in my class as follows:
onBeforeChange(change) {
console.log('calling beforeChange');
}
onChange(newCode) {
this.setState({
code: newCode
});
}
Author of react-codemirror2 here. I stumbled upon your question and wanted to follow up with a detailed answer as there are some breaking changes in 3.x. The component now ships with an UnControlled and Controlled variant based on different use cases. I see you are calling setState within the onBeforeChange callback. In your case, I'd suggest leveraging the controlled component as such...
import {Controlled as CodeMirror} from 'react-codemirror2'
<CodeMirror
value={this.state.value}
options={options}
onBeforeChange={(editor, data, value) => {
this.setState({value}); // must be managed here
}}
onChange={(editor, metadata, value) => {
// final value, no need to setState here
}}
/>
With the controlled variant, managing state is required on the value prop to see any changes.
Additionally, the UnControlled component also has an onBeforeChange callback as well, yet with different behavior as such...
import {UnControlled as CodeMirror} from 'react-codemirror2'
<CodeMirror
value={value}
options={options}
onBeforeChange={(editor, data, value, next) => {
// hook to do whatever
next();
}}
onChange={(editor, metadata, value) => {
}}
/>
Here however, onChange will be deferred until next is invoked if onBeforeChange is specified. If not, onChange will fire regardless. Important to note, though, with the UnControlled variant, the editor will always react to input changes - the difference will simply be if onChange is called or not.
These changes were inspired due to the needs of the community and I encourage you to open an issue should anything not be working as you expect.
It turns out react-codemirror does NOT expose the beforeChange event.
However, react-codemirror2 does. A switch of library fixed this for me!
My final callback code:
onBeforeChange(cm, change, callOnChange) {
const options = Object.assign({}, DEFAULT_OPTIONS, this.props.options)
if (options.singleLine) {
var after = change.text.join('').replace(/\n/g, '')
change.update(change.from, change.to, [after])
}
callOnChange()
}
onChange(cm, change, code) {
this.setState({ code })
if (this.props.onChange) {
this.props.onChange(code)
}
}

Categories

Resources