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

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="")

Related

Improve textfield render performance

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

ReactJS autocomplete from React Bootstrap not working

I'm trying to build an autocomplete search field, using this form component from React Bootstrap, which is rendered as a input by the browser.
Here's what I did in my React component:
<FormControl
id="frenchToEnglishInput"
placeholder="type a word..."
aria-label="Recipient's username"
autocomplete="on"
data={frenchToEnglishWords}
onChange={this.fetchFrenchToEnglish}
renderItem = {item => {
return (
<div>
{item}
</div>
);
}}
/>
the frenchToEnglishWords array is declared outside the component as a var, as I intend to update it as I type some value into the input field.
Now here is the function that triggers the onChange event :
fetchFrenchToEnglish = async () => {
if(document.getElementById("frenchToEnglishInput").value!==''){
axios.get(dictionaryURIs.english.French_English+""+document.getElementById("frenchToEnglishInput").value)
.then(response => {
frenchToEnglishWords = response.data
})
}
}
The request is made from MongoDb, after setting up an autocomplete index on a collection, but this part works fine.
My question is, why is the only "autocomplete" I'm getting is the one made of the previous words I've typed ?
Or maybe the array I'm using as input data must be a const (as I've seen in many examples) and not a var ?
When I do type in some word, I do not get any autosuggestion from the frenchToEnglishWords, which is being updated from the DB.
You need to use State!
This is not working because the data field is not a State, you need to bind the fetchFrenchToEnglish function to the data state.
But first of all, there's no reason to use var, because the most differences between them is just scope and immutability, here is more about it.
Also, you can use hooks like useState and useRef to no use getElementById.

Two way binding for Formik Field and custom component in React

I am trying to pass my own functional component as a component attribute to a Formik Field in React. The problem is I don't know how to bind the Formik Field's value with the value in my custom component. Here's what I tried:
Form.js
// other Formik Form code...
<Field
className="form-control"
name="NetWeight"
component={IntegerComponent}
></Field>
//...
IntegerComponent.js
function IntegerComponent({ field, className }) {
const onChangeHandler = event => {
const returnVal = (event.target.validity.valid) ? event.target.value : inputValue;
setInputValue(returnVal);
};
return <input
type="text"
pattern="[0-9]*"
className={className}
onChange={onChangeHandler}
value={inputValue}
/>
}
I want to bind Formik's field.value which I am destructuring from the props, to my NumberComponent's input's value. I tried doing field.value = returnVal but it didn't work as well. I know I can use onBlur and onChange handlers for above purpose than creating my functional component but the way my project is built, if I can make this work, it will help reducing a lot of redundant code. Any thoughts or suggestions?

Read value of imported form component upon submit (without storing in state)

I developed a component for ReactJS as to be used as a form item in conjunction with Antd's form. But, the onFinish callback function returns undefined for the value of my custom component. Probably, Antd's form is not being able to retrieve the value from my component. That does not happen when I am only using Antd components as form items.
On the example below, MultipleEntry is the component I have developed. MultipleEntry has a nested TextArea component.
function Sandbox(props) {
return (
<>
<Form onFinish={(values) => console.log(values)}>
<Form.Item name={'myComponent'} >
<MultipleEntry />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</>
);
}
function MultipleEntry(props) {
const [value, setValue] = useState([]);
const Split = string =>
{
setValue(String(string).split(';'))
}
return (
<TextArea
onChange={(e) => {Split(e.target.value)}}
/>
);
}
I thought about two alternatives here:
Storing the values of MultipleEntry in its internal state.
Storing the values of MultipleEntry on the parent component.
But, I dont want to store the values anywhere on the client's state, since the data inside the MultipleEntry component would be too big and impactful for performance.
How can I use Antd form's API to make it read the value of MultipleEntry as a form field, and populate the input parameter of onFinish?
Antd's FormItem behaves like a high-order component and passes some props to the child component.
So, if you are using any HTML form field elements like input, then FormItem will pass the onChange and value as props to input. Hence FormItem will control the value and onChange event of that element.
But having said above you will not be able to use the styling if some validation error is there in the FormItem.
Similarly Antd's component also usages native HTML form field elements and usages value and onChange. But on top of that, you will get the styles if there are any validation errors.
Here it comes your specific case, you can use onChange and value from props and utilize the same as following
function MultipleEntry(props) {
const Split = e => {
e.target.value = String(e.target.value).split(",");
props.onChange(e);
};
return <input value={props.value} onChange={e => Split(e)} />;
}
If you are using any native HTML form elements then just split the props and pass it across the element like below
function MultipleEntry(props) {
return <input {...props} />;
}
And for splitting you can use getValueFromEvent and then split the value there.
Here is the link for the modified sandbox to especially solve your problem https://codesandbox.io/s/agitated-lake-65prw

Changing input value by reference

So the normal way of handling inputs is to have a state variable for it and change it, like this:
const [title, setTitle] = useState('');
<Form.Input
label="Title"
value={title}
onChange={(e, data) => {
setTitle(data.value);
}}
/>
The problem comes when there are more complex forms, and where the complete form is in a single variable and inside many different components.
The way I figured it "works" is to include the object where the value is and the name of the property, that way I can change it (but even then it makes things a bit tricky):
<Form.Input
inline
label="Subtitle"
value={item.instructions_data.subtitle}
onChange={(e, data) => {
onChange(item.instructions_data, data.value, 'subtitle');
}}
/>
// onChange example
const onChange = (source, value, field) => {
source[field] = value;
setItem(item);
};
This way works, but it is far from elegant and it makes a lot of unnecessary boilerplate.
What is the better way of doing this? Can I somehow get a reference object from event in onChange?
Thanks for all the help!

Categories

Resources