I have a mapped list of input fields:
<FormControl
name={row_index}
value={barcode.barcode}
placeholder="Barcode - then Enter"
onChange={this.onChange}
onKeyPress={this._handleKeyPress}
disabled={barcode.submitted}
/>
I am currently using onKeyPress to handle submit:
_handleKeyPress = (e) => {
if (e.key === 'Enter') {
const name = e.target.name;
const barcodes = this.state.barcodes;
const this_barcode = barcodes[name];
let apiFormatted = {"barcode": this_barcode.barcode, "uid": this.props.currentSession}
this.postBarcodeAPI(apiFormatted, name)
}
}
I am attempting to focus on the next input field after the current one is successfully submitted. React documentation has an example for manually setting focus on a single input field using ref={(input) => { this.textInput = input; }} />. I have tried using this[‘textInput’+‘1’].focus() (using computed property names, but am getting an error that function is invalid.
EDIT
Per Chase's answer, I am linking to the autofocus documentation, although it doesn't work in this case.
https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/autofocus
My working solution:
const focusing = index === lastSubmittedIndex + 1 ? true : false;
const refText = focusing || index === 0 ? input => input && input.focus() : null;
<FormControl
name={row_index}
value={barcode.barcode}
placeholder="Barcode - then Enter"
onChange={this.onChange}
onKeyPress={this._handleKeyPress}
disabled={barcode.submitted || barcode.apiCalling}
inputRef={refText}
/>
What I corrected in my code:
1) I am supposed to use inputRef instead of ref for Bootstrap's FormControl component, see here.
2) I am using ilya-semenov's very neat code.
Update
I have other buttons on the page, when user presses them and is at bottom of page, page jumps up to top. Not sure why.
Unless you've set a ref with the key textInput1 then this won't work. Another suggestion would be to move all of the inputs into a separate component and then you can use this.props.children to traverse all your inputs and grab the input at whatever position you want.
EDIT:
You can also use the autoFocus prop to determine if an input should be focused or not.
Related
I have a form that has textboxes that are prepopulated from an WebAPI. When I try to delete the text in the textbox to make a change it doesn't delete the prepopulate text. If I try to type over the text, I can see only the first letter of the word I'm typing in the console, but nothing changes on the UI: It' like the textbox is in readonly mode WHICH IT IS NOT
const Details = () => {
const [ server, setServer] = useState([]);
useEffect(() = > {
getServerNames();
}
const getServerName = async() => {
//gets the list of server and their details from the API
}
const serverNameChange = (e) => {
setServer(e.target.value);
}
return (
<div>
{ details.map((data) => {
<input type="text" name="server" onChange={serverNameChange} value={data.serverName} />
))}
</div>
)
};
What am I missing to allow the users to edit the textbox? The textbox is prepopulated with data, however, it can be changed. This is only happening on textboxes that are prepopulated. I don't want to click an Edit button, I want to give the user the ability to make a change in the textbox and then save it.
That might be due to the fact, that data.serverName never changes. It’s basically a static value. If you set the value of an Input, you have to handle the changes (when typing) in the onchange event.
From what I assume, according to your code is that you have multiple input boxes with preloaded values in them and you want to change your serverName if one of them get changed by the value that is in the textinput.
If so, map your details into a state variable:
const [serverNames, setServerNames] = useState(details.map( data => data.serverName));
Map the inputs from your state variable like so:
{serverNames.map((name,index) => {
< input type="text" name="server" onChange={(e) => {updateServerState(e, index)}} value={serverNames[index]} />
}
}
And your updateServerState method looks like that:
updateServerState(e, index) {
let myStateData = [...serverNames];
myStateData[index] = e.target.value;
setServerNames(myStateData);
setServer(e.target.value);
}
Caution: I haven‘t tested the code, just wrote it down. But that should give you an idea of how to solve your issue.
TL;DR; Never use non-state variables for a dynamic value.
I'm creating a React form with Material UI. My goal is to force the user to answer all the questions before they can click the download button. In other words I'm trying to leave the download button in the disabled state until I can determine that values are set on each field. I've tried to get this working with and without react-hook-form.
What I've tried
Without react-hook-form...
I have my example in coding sandbox here:
https://codesandbox.io/s/enable-download-button-xwois4?file=/src/App.js
In this attempt I abandoned react-hook-form and added some logic that executes onChange. It looks through each of the formValues and ensures none of them are empty or set to "no answer". Like this:
const handleInputChange = (e) => {
// do setFormValues stuff first
// check that every question has been answered and enable / disable the download button
let disableDownload = false;
Object.values(formValues).forEach((val) => {
if (
typeof val === "undefined" ||
val === null ||
val === "no answer" ||
val === ""
) {
disableDownload = true;
}
});
setBtnDisabled(disableDownload);
The problem with this approach, as you'll see from playing with the UI in codesandbox, is that it requires an extra toggle of the form field value in order to detect that every field has been set. After the extra "toggle" the button does indeed re-enable. Maybe I could change this to onBlur but overall I feel like this approach isn't the right way to do it.
Using react-hook-form
With this approach...the approach I prefer to get working but really struggled with, I ran into several problems.
First the setup:
I removed the logic for setBtnDisabled() in the handleInputChange function.
I tried following the example on the react-hook-form website for material ui but in that example they're explicitly defining the defaultValues in useForm where-as mine come from useEffect. I want my initial values to come from my questionsObject so I don't think I want to get rid of useEffect.
I couldn't figure out what to do with {...field} as in the linked material ui example. I tried dropping it on RadioGroup
<Controller
name="RadioGroup"
control={control}
rules={{ required: true }}
render={({ field }) => (
<RadioGroup
questiontype={question.type}
name={questionId}
value={formValues[questionId]}
onChange={handleInputChange}
row
{...field}
>
but I get an MUI error of : MUI: A component is changing the uncontrolled value state of RadioGroup to be controlled.
Also, I don't see that useForm's state is changing at all. For example, I was hoping the list of touchedfields would increase as I clicked radio buttons but it isn't. I read that I should pass formState into useEffect() like this:
useEffect(() => {
const outputObj = {};
Object.keys(questionsObject).map(
(question) => (outputObj[question] = questionsObject[question].value)
);
setFormValues(outputObj);
}, [formState]);
but that makes me question what I need to do with formValues. Wondering if formState is supposed to replace useState for this.
I have a button that is disabled if a state is false and the state is change if some conditions are not met on each item in an array, one of the condition is that a textfield should contain something, I check for that condition each time the text change. The condition work but each time I change the state the keyboard is dismiss. ie: each time I enter or remove the first letter of the word.
const [canSave, setCanSave] = useState<boolean>(true);
//this function is called each time one of the textfield is changed
const updateCanSave = () => {
for (let i = 0; i < currentSteps.length; i++) {
const step = currentSteps[i];
if (
(step.name.length > 0 && step.media === undefined) ||
(step.name === "" && step.media !== undefined)
) {
setCanSave(false);
break;
}
setCanSave(true);
}
};
return (
//the flatList renderItem contains many textfield and imagePicker
<FlatList
data={array}
...
ListFooterComponent={
<Button on Press={save} disable={!canSave}>Save</Button>
}
/>
)
Though you cannot do that but there are ways to go through it.
1) you can set autoFocus={true} for the input field. But, this is a little buggy. Keyboard stills closes after every change but opens just after that very quickly but not so good.
2) create some other dict (other than state) and store the value of different input fields in that and change the state when the input field loses focus (you can check that by using onBlur prop of input field).
I have an input for an amount in react-final-form. If it's not filled, I need to set its form value to zero. I can't pass an initial value to it as if it hasn't been filled by the user, the input itself should stay empty.
In react-final-form docs there's a parse function. But it works only if the field has been touched (filled and then cleared by the user). Is there any way to parse untouched fields and set them to zero in form values, without updating the input?
Here is my code:
<Field
name="amount"
component={CurrencyInput}
parse={value => (value ? value : 0)}
/>
And here is the link to my codesandbox.
if by doing this what you are trying to accomplish is that the object displayed in the <pre> has a default value of zero when the input is empty you could use a state variable and inside the parse keep updating its value
const [amount, setAmount] = useState(0);
const onInputChange = e => {
e === "" || e === undefined ? setAmount(0) : setAmount(e);
};
and in the Field tag
<Field
name="amount"
component={CurrencyInput}
parse={value => onInputChange(value)}
defaultValue={amount}
/>
check the codesandbox here:
https://codesandbox.io/s/react-final-form-wreact-number-format-and-parse-22q3n
Hope this Resolves the issue
I have a text input field in my React app and I want to validate its input so I have a validator function working in a handler for the onChange event. I also want to trim whitespace when a value is pasted into the input and then have that run through the onChange validator.
Currently my JSX looks like this:
handleChange(event) {
let { name, value } = event.target
if (typeof this.props.validatorFunction === "function") {
value = this.props.validatorFunction(value) // the validator function in this case is {value => value.replace(" ", "-").toLowerCase()}
}
this.setState({
[name] : value
})
}
handlePaste(event) {
let { name, value, selectionStart, selectionEnd } = event.target
let pastedValue = event.clipboardData.getData("text")
let pre = value.substring(0, selectionStart)
let post = value.substring(selectionEnd, value.length)
value = (pre + pastedValue + post).trim()
this.setState({
[name] : value
})
}
render() {
return (
<input type="text" name="myInput" value={this.state.myInput} onChange={this.handleChange} onPaste={this.handlePaste}></input>
)
}
Regular typing works as expected however, when I paste something such as "test string" I get "test-stringtest string". "test String " with the trailing white-space I get "test-stringtest string ". This works except for the doubling up of the untrimmed but somehow lowercased but not replaced original string. How do I fix this, or what is the proper workflow for this kind of validation using React controlled components?
You need to call event.preventDefault() after handling your paste event. As it is, the paste event is changing the state in handlePaste and then going on to add the pasted text to the input, triggering handleChange.
As requested, I am providing more details on the solution.
It turns out that for my purpose, having an onPaste handler is superfluous. If I removed the onPaste handler, the doubling issue would be resolved, and paste behaviour inherently remains. An example of the code I would write for this today is here: https://jsfiddle.net/tvandinther/sLav0chj/31/
function ValidatedInput(props) {
const format = (value) => typeof props.formatFunction === "function"
? props.formatFunction(value)
: value
return (
<input
value={props.value}
onChange={event => props.setValue(format(event.target.value))}
/>
)
}
Suppose you do need to keep the onPaste handler. In that case, the solution described is to call your shared logic (in this case, formatting the string and setting the state) in both onPaste and onChange handlers but ensure that event.preventDefault() is called in the onPaste handler.