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!
Related
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="")
I'm finishing up the final steps of my form however I have encounter an issue when trying to display the current name of the user in the defaultValue variable of the TextField from MUI.
If a value starts on ""/null or basically empty the defaultValue will stay empty doesn't matter how many times you update that value from ""/null to something, is there a way to fix that?
I have this:
{console.log(tempName)}
<TextField
required
margin="dense"
label='Nombre'
defaultValue= {tempName}
onChange={handleChangeName} />
tempName starts empty but then I update it in a useEffect I have that brings the data of the document of the user I'm log in atm, even after having a value the defaultValue stays empty/null/"".
There's something else I did notice though, if you use the user.displayName directly it does works but only until you refresh (because when you refresh user goes back to null and then like .2 segs is back to his value)
this is how it looks when I use user before refresh
since user brings both name and lastname (displayName, basically) I wanted to split it and all the user information is in a document :
const [tempName, setTempName] = useState("");
const [tempLastName, setTempLastName] = useState("");
const [tempEmail, setTempEmail] = useState("");
const [tempPhone, setTempPhone] = useState("");
const [tempIdType, setTempIdType] = useState("");
const [tempIdTypes, setTempIdTypes] = useState("");
useEffect(() => {
const userDoc = db.collection('usuarios').doc(user.uid);
userDoc.get().then(doc => {
if (doc.exists) {
const tempData = [];
const data = doc.data();
tempData.push(data);
setTempName(tempData[0].name)
setTempLastName(tempData[0].lastName)
setTempEmail(tempData[0].email)
setTempPhone(tempData[0].phone)
setTempIdType(tempData[0].id)
setTempIdTypes(tempData[0].idType)
setOldPassword(tempData[0].password)
}
});
}, [user])
This is how it looks in the firebase
Is that how is suppose to work?
I would have think that defaultValue will update to the value you are giving it... Thank you, I didn't find anything related with this on TextField documentation. All I know is that they add initialized values from the very beginning and not values that get "updated" with time, I just wanted to display the current information of the user in the form.
Have a look at controlled mode example, defaultValue doesn't work on subsequent renders, if you want to control and update the value, use value prop.
Default value is usually used in uncontrolled mode where the value is controlled by the DOM element, not react itself, and the only way to alter it is setup an initial value when it is first mounted. The value of the uncontrolled component can only be accessed (without using ref) when submitting the form or validating the value, if you want to update the state on the fly based on the value as the user types, use controlled mode.
value={value}
onChange={e => setState(e.target.value)}
what's the purpose of defaultValue then? if you can just use placeholders for stuff like "Name" and then use value instead of defaultValue
If you use a placeholder, the default value of that TextField won't be submitted in the form (probably not what you expect here).
I have a problem that I've been trying to solve for the past couple of days:
I'm using a Context Provider for form fields and for whatever reason fields keep overwriting each other when I use memo.
Provider:
export const Context = React.createContext();
function Form_Provider({ values, onChange, children }) {
return (
<Context.Provider value={{ values, onChange }}>
{children}
</Context.Provider>
)
});
export default Form_Provider
Field:
function Field({ label, name }) {
const { values, onChange } = useContext(Context);
return (
<Memorable
label={label}
onChange={({ target: t }) => onChange({ name, value: t.value })}
value={values[name]}
/>
);
}
const Memorable = React.memo(props => {
return (
<Form.Item label={props.label}>
<Input
value={props.value}
onChange={props.onChange}
/>
</Form.Item>
</>
)
}, ({ value: newValue}, { value: oldValue }) => newValue == oldValue)
Form
const [formValues, setFormValues] = useState({ field1: 'Foo', field2: 'Bar' });
<Form.Provider
values={formValues}
onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))
>
<Form.Field name='field1' label="Field 1" />
<Form.Field name='field2' label="Field 2" />
</Form.Provider>
(Tried to simplify it as much as possible)
In my actual code I've added a json print prettifier to track the state for every field and it works out until every single field when i only edit one field. However, once I start editing another field the first field I've edited goes back to its original state and/or some other weird in between state from it's past.
If I dont use Memo it works but that can't be the solution as I'll be working with a lot of fields and that would cause a lot of re-rendering.
Anyone any idea what's going on here?
Addition:
I've already tried using an internal reducer for this and passing down a dispatch function. As long as I don't try to manage the state outside of the provider everything works.
I'm pretty sure the issue is that you are memoizing the original values of formValues from here:
onChange={({ name, value }) => setFormValues({...formValues, [name]: value }))
Say field1 has a change in it's value. It calls the onChange handler, which merges ...formValues - the values that existed when the component mounted - with the new value for field1.
Now the equality function in React.memo for field1 returns false, because the value is different. That particular field re-renders to recieve its new value, and also the new values of ...formValues. The other fields, however, have not rerendered. For them, ...formValues still means the value of the state as it existed the last time they re-rendered, which was when the component mounted.
If you now change the value of field2, it will set the state to the result of merging the original state with the new value of field2. Hence field1 is reset because its value has now changed again back to the original value.
A simple solution to this would be to use the callback version of setState, which always uses the state's current value:
onChange={({ name, value }) => setFormValues(fv => {...fv, [name]: value }))
However, I would be tempted not to do this, and instead get rid of the memoisation altogether. This is because your equality function does not actually accurately reflect the way that props provided to the component change. I believe the performance gains here are also negligible, because the component is so small and does not render any additional components itself.
Assuming there's no animation tied to the value change, it is very cheap to perform and does not make a good candidate for memoisation, which also escapes the built in React optimisation. You should think carefully to decide if you really need it before implementing it.
I have a table of data that is rendered via an api call. It displays 4 values from the initial object. I mainly care about the name value as this is what's important later in a post request.
Each row in the table has a checkbox. When the checkbox is selected (true) the name associated with that checkbox is added to an object called selectedFields, as an object. For example If I select the checkbox with name id it creates an object store like:
"selectedFields": {
"id" : {}
}
This works fine and well. However, I've added 3 input boxes that are associated with each name. The inputs are lengthType, size, maxArrayElements, which are of course user selectable.
What I'm having trouble with is adding these values back to the object so it looks like:
"selectedFields": {
"id": {
lengthType: Variable,
size: 1,
maxArrayElements: 1
},
"price": {
lengthType: Fixed,
size: 10,
maxArrayElements: 1
}
}
How can I add these 3 values back to the name object that was created so it looks like the above example?
I don't want to post a wall of code, so I'm posting the checkbox function that handles creating the selectedFields object with the appropriate selected names. I suspect that the input values should get added here somehow, but I'm not sure.
checkbox = ({ name, isChecked }) => {
//handle check box of each fieldName
const obj = this.state.fieldNames.find(field => field.name === name);
if (isChecked === true) {
//checked conditional
this.setState(
{
selectedFields: {
...this.state.selectedFields,
[name]: {
...obj
}
}
},
() => {
console.log(
"callback in isChecked if conditional",
this.state.selectedFields
);
}
);
} else {
const newSelectedFields = this.state.selectedFields;
delete newSelectedFields[name];
this.setState(
{
selectedFields: newSelectedFields
},
() => {
console.log(
`box unchecked, deleted from object --->`,
this.state.selectedFields
);
}
);
}
};
You will have to make the first dropdown selection to view the data.
CodeSandbox link here
Answer
You need to change a few things because nothing is stating where to assign the new state outside of the root state object.
Handlers in your Index.js:
Your handlers for the change events aren't looking for the name of the object to determine if it exists or not. If you want to add it to the specified child object you better be sure it's there.
We adjust the handlers to take an object name and setState with object.assign on that specific object if it exists:
note: since lengthType doesn't have a name property we simply provide it with a string. e.currentTarget will provide the option span, not the root Dropdown element, so even supplying a name property to that component wouldn't allow us to use e.currentTarget.name - you may want to consult the Semantic UI documentation if you would prefer something different. I gave it a quick scan but didn't want to deep dive it.
handleChange = (e, obj_name) => {
if (this.state.selectedFields[obj_name]) {
let selectedFields = Object.assign({}, this.state.selectedFields);
selectedFields[obj_name] = Object.assign(
this.state.selectedFields[obj_name],
{ [e.target.name]: e.target.value }
);
this.setState({ selectedFields });
}
};
onLengthTypeChange = (e, obj_name) => {
if (this.state.selectedFields[obj_name]) {
let selectedFields = Object.assign({}, this.state.selectedFields);
selectedFields[obj_name] = Object.assign(
this.state.selectedFields[obj_name],
{ lengthType: e.currentTarget.textContent }
);
this.setState({ selectedFields });
}
};
The above, of course, won't work if you don't adjust your onChange events on your components so that, in addition to your event object, they also send your object name.
Handlers in your Component file:
Note: It was odd because in your Index.js file you seemed to half do this with lengthType but you weren't passing over additional data. You can't simply pass parameters into a handler - to get it to work you need to pass an anonymous function to the onChange properties that will take the event and pass it on to the handler functions with your object name:
<Table.Cell>
<Dropdown
placeholder="Pick a length Type:"
clearable
selection
search
fluid
noResultsMessage="Please search again"
label="lengthType"
multiple={false}
options={lengthTypeOptions}
header="Choose a Length Type"
onChange={e => onLengthTypeChange(e, name)}
value={lengthType}
required
/>
</Table.Cell>
<Table.Cell>
<Input
onChange={e => handleChange(e, name)}
value={this.state.size}
type="number"
name="size"
min="1"
placeholder="1"
required
/>
</Table.Cell>
<Table.Cell>
<Input
onChange={e => handleChange(e, name)}
value={this.state.maxArrayElements}
type="number"
name="maxArrayElements"
placeholder="1"
min="1"
max="100"
required
/>
</Table.Cell>
Once these things are adjusted, the code will update the specified properties on the child objects after the corresponding checkbox is selected.
Final Note:
I did not adjust it to save the previous state if you uncheck and then check the box. It wasn't specified in your question and I don't want to make assumptions.
Code Sandbox:
The adjusted code sandbox: https://codesandbox.io/s/createqueryschema-table-rewrite-bwvo4?fontsize=14
Additional Recommendations:
In your initial state your selectedFields is declared as an Array and then it is promptly turned into an Object when any checkbox is selected. I would suggest not doing this. Changing data types on a property during the course of running an application is very much asking for trouble.
When a checkbox is loaded you provide a checkbox function from your Index.js file. This is simply called box in your component. I would suggest keeping the names of properties and state equivalent when passing down from parent to child. It is much, much, much easier for someone else to come in and maintain if they have to - not to mention easier to retain your own sanity.
The above checkbox function takes props from child and passes them up to the parent. This would be the place to pass your collected data into a cache on the parent, into local/session storage, or whatever you want to do with your data. You could instead write code to the effect of: if the checkbox is selected when an input handler is called do a save - but I would say that it would probably be best on render since the screen is constantly updating anyway and you have the checkbox function readily passing props currently. This is preference, so it's your call
Good luck! Hope this helped!
I've been working on a simple react-redux todo example for a class and I came across several warning messages that show in the console everytime I check and uncheck a checkbox input.
You can see the warnings in the following images.
I also did a google search for the warning message but couldn't find any solution that works. Also, what stroke my attention was that it looks like it was trying to access every property of the native event, and DOM element.
This is the code for the presentational component that has the input checkbox
class TodoItem extends React.Component {
state = {
isChecked: false
};
handleCheckbox = () => {
this.setState({
isChecked: !this.state.isChecked
});
};
render() {
const { todos, onItemClick } = this.props;
const { isChecked } = this.state;
return (
<div>
<ul>
{todos.map((todo, id) => {
return (
<li key={id} onClick={onItemClick}>
<input
onChange={this.handleCheckbox}
type="checkbox"
checked={isChecked}
/>
<label>
<span />
{todo.textInput}
</label>
</li>
);
})}
</ul>
</div>
);
}
}
export default TodoItem;
I uploaded the example on CodeSandbox as well: https://codesandbox.io/s/k0mlxk1yqv
If you want to replicate this error you need to add an Item to the todo List and click the checkbox to check and uncheck a couple of times.
If anyone has any idea why this warning signs keep appearing and how to disable them I would appreciate your input very much :)
This happened because the event implicitly passed to onItemClick is used in an asynchronous context.
As Andre Lemay said, you should assign your needs to local variables and reference them.
In my case, I had this code:
handleInput = e => { // <-- e = synthetic event
this.setState(state => ({ // <-- asynchronous call
data: {
...state.data,
[e.target.name]: e.target.value // <-- this was causing the warnings (e.target is in an asynchronous context)
}
}));
};
Then I changed it to:
handleInput = e => {
const { name, value } = e.target; // <-- moved outside asynchronous context
this.setState(state => ({
data: {
...state.data,
[name]: value
}
}));
};
I'd suggest trying two solutions:
First change
onChange={this.handleCheckbox}
to
onChange={() => this.handleCheckbox()}
If that won't work, in 'handleCheckbox' add event.persist(); Like this:
handleCheckbox = (event) => {
event.persist();
this.setState({
isChecked: !this.state.isChecked
});
};
This may be a little late, but I just came across the same problem and solved in a way that I think might be better than Adam Orlov's answer. I don't believe either answer is directly applicable to the asked question, but this comes up when googling about synthentic events and checkboxes so it's as good a place as any...
I believe Adam is correct in his belief that React will essentially clear all properties of the SyntheticEvent object (which makes sense, since React is telling us that it's reusing the object).
However, unless you need the entire object, I don't think calling event.persist() is the best solution, as according to the documentation, that will remove the object from the pool (presumably they put it there for a good reason).
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.
Instead of doing this, if you only need one or two values from the event object, you can just assign those to local variables, and then reference the local variables inside your own function, like this:
<input type="checkbox" onChange={(event) => {
let checked = event.currentTarget.checked; //store whatever values we need from from the event here
this._someOtherFunction(checked)
} />
In this way, you don't have to restructure your code in any way to avoid doing anything async that relies on event data, and you also don't have to worry about potential performance impacts as you allow React to do what it wants with the event pool.
Similar problem here though my setup is, functional component, Material UI <Textform /> input.
The guy above that mentioned event.persist();, thank you that worked for me, but the first suggestion had no noticeable affect, not sure if thats because Im using functional components and not class components. (I dont use class components anymore, only functional with hooks)
Also note the warning info suggested to use event.persist(). My issue was I was capturing form input using onChange and storing input into my state, after about the second or third character it would throw errors and also crash my app.
Before:
const handleChange = (e) => {
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
After:
const handleChange = (e) => {
e.persist();
setState((form) => ({
...form,
[e.target.name]: e.target.value,
}));
};
So it appears this is the correct solution to a similar issue, while I was not using a checkbox, I was using a form input, a Material-UI <TextField />. I can remove the single line of
e.persist();
and the code will fail again, add it back, everything is good no crashing no warnings.
for the ones that came to this problem with react native.
i face this problem on a navigation
PersonalProductsScreen.navigationOptions=({navigation})=>{
const handleEditButton=navigation.navigate.bind(this,"EditProduct")
return {
headerTitle:"My Products",
headerRight:<CustomHeaderButton
iconName="ios-add"
title="Add"
iconSize={26}
color={colors.bright}
onPress={handleEditButton}
/>
}
}
pay attention to the method i used . I was trying to bind the navigate method.
This is the refactor:
const handleAddButton=()=>navigation.navigate("EditProduct")