I have a form. In the form I getting brandName, supplierName, and date of expiry from the user.
I am usinf TextField from mui library and submit button.
I want to disable submit button on empty form fields and enable it when user filled all the inputs
here is my code for declaring useStates
const[brandName, setBrandName] = useState("");
const[supplierName, setSupplierName] = useState("");
const[expiryDate, setExpiryDate] = useState(null);
const[brandNameError, setBrandNameError] = useState(false);
const[supplierNameError, setSupplierNameError] = useState(false);
const[expiryDateError, setExpiryDateError] = useState(false);
const[submitButton, setSubmitButton] = useState(true);
and here is all function which i used to validate my inputs
// checking brandName Error
const brandNameValidateOnBlur = ()=>{
if(brandName === ""){
setBrandNameError(true);
}
}
// checking supplier name error
const supplierNameValidateOnBlur = ()=>{
if(supplierName === ""){
setSupplierNameError(true);
}
}
// checking expiry date
const expiryDateValidateOnBlur = ()=>{
if(expiryDate === ""){
setExpiryDateError(true);
}
}
// now checking all inputs again if all inputs are good then
// button should be enabled
const checkAllInputs = ()=>{
if(brandName !== "" && supplierName !== "" && expiryDate !== ""){
setSubmitButton(false);
}else{
setSubmitButton(true);
}
}
and here is the rest of code
<TextField fullWidth id="productName" label="Product Name"
value={brandName.toLowerCase()} variant="outlined"
onChange={(data)=>{setBrandName(data.target.value.toUpperCase());checkAllInputs()}}
onBlur={brandNameValidateOnBlur}
onFocus={()=>setBrandNameError(false)}
error={brandNameError}
helperText = {brandNameError ? "Enter Brand Name" : ""}
/>
<TextField id="supplierName"
label="Supplier Name"
value={supplierName.toLowerCase()} variant="outlined"
onBlur={supplierNameValidateOnBlur}
onFocus={()=>setSupplierNameError(false)}
error={supplierNameError}
helperText={supplierNameError ? "Enter Supplier Name " : ""}
onChange={(data)=>{setSupplierName(data.target.value.toUpperCase());checkAllInputs()}} />
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DesktopDatePicker
label="Date Expiry"
inputFormat="MM/DD/YYYY"
value={expiryDate}
onBlur={expiryDateValidateOnBlur}
onFocus={()=>setExpiryDateError(false)}
error={expiryDateError}
helperText={expiryDateError ? "Enter Expiry Date ": ""}
onChange={(selectedDate)=>
{setExpiryDate(selectedDate.format("MM/DD/YYYY"));checkAllInputs()}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
<Button type='button' variant='contained' id="submitButton" disabled={submitButton}
style={{backgroundColor:'orangered'}} onClick={addData}>Add Data</Button>
now problem is when i enter a single word my useState is updated but my checkAllInputs not working as i want to be like if i enter a word my checkAllInputs method runs before setting of state as i enter second entry then it works like I want to be so i did not know what i am doing wrong
I TRY useEffect hook like that
useEffect(()=>{
checkAllInputs();
},[brandName, supplierName, expiryDate]);
its working fine as i accepted but I read that it should be a expensive to use useEffect
and an other approach which i use
const checkAllInputsWithDom = ()=>{
let brandNameEntry = document.getElementById("brandName").value;
let supplierNameEntry = document.getElementById("supplierName").value;
let expiryDateEntry = document.getElementById("expiryDate").value;
if(brandNameEntry !=="" && supplierNameEntry !== "" && expiryDateEntry !== ""){
setSubmitButton(false);
}else{
setSubmitButton(true);
}
}
it is working out of box but I thing in react this is against react principles to direct manipulate
dome elements can I do it with out using useeffect hook like only with my metohd and one more thing
this did not set expiryDate error in date field
As long as you're keeping the input values in state, you don't need another variable storing the button state as well. You can calculate it at render time. (Read more about avoiding redundant state here.)
You could do something like this:
const Component = () => {
const[brandName, setBrandName] = useState("");
const[supplierName, setSupplierName] = useState("");
const[expiryDate, setExpiryDate] = useState("");
const disabled = brandName.length === 0 && supplierName.length === 0 && expiryDate.length === 0
return (
<>
<input value={brandName} onChange={(e) => setBrandName(e.target.value)}/>
<input value={supplierName} onChange={(e) => setSupplierName(e.target.value)}/>
<input value={expiryDate} onChange={(e) => setExpiryDate(e.target.value)}/>
<button disabled={disabled}>Button</button>
</>
)
}
here is the link for codesandbox I have a suggestion for you to refactor this 6 use States with just one useState as an object having all the values like the following:
const initialValues = {
brandName: "",
supplierName: "",
expiryDate: "",
brandNameError: "",
supplierNameError: "",
expiryDateError: ""
};
and then update it with a generic handleInputChange like below:
const handleInputChange = (e) => {
//const name = e.target.name
//const value = e.target.value
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
let isEmpty = Object.values(values).some((x) => x === "");
console.log(isEmpty);
setIsDisabled(isEmpty);
};
FOR YOUR SOLUTION
You will have to just check all the values and just create a boolean with isDisabled so when all of those values are empty then it will only set to false.
I have created a code snippet for you, it needs some improvement but it will serve your purpose. Link already at the top and here as well
as suggested by Eduardo Motta de Moraes
I used this for making my button enabled after all inputs validate
const disabled= brandName.length === 0 || supplierName.length === 0 || expiryDate.length === 0;
this solve my problem
thanks for Eduardo Motta de Moraes for this
Related
could you help me?, I'm developing with react native and typescript, I need to do this:
I have a checkBox that tells the user to check if his name is the same as his credit card, if he clicks the checkbox, a TextInput with his name is autocompleted,
if you don't click the checkbox that he can type his name.
I have a variable like: user_name
I don't have code because I don't know how to do it, but I think it goes something like this:
const [checked, setChecked] = useState(false)
...
return(
<CheckBox
checked={setChecked}
onPress={}
checkedIcon='dot-circle-o'
uncheckedIcon='circle-o'
checkedColor='blue'
uncheckedColor='blue'
/>
<TextInput
placeholder='Nombre completo'
value={}
onChangeText={}
)
We could implement this as follows under the assumption that the name of the credit card is available. Let us call it creditCardName.
My example uses react-native-checkbox and the standard TextInput component. If you are using a different component, then the overall pattern still stays the same.
const [isChecked, setChecked] = useState(false);
const [userName, setUserName] = useState('');
return (
<CheckBox
value={isChecked}
onPress={(newVal) => setChecked(newVal)}
/>
<TextInput
editable={!isChecked}
placeholder='Nombre completo'
value={isChecked ? creditCardName : userName}
onChangeText={setUserName}
/>
)
The key-parts are as follows:
Let the TextInput be editable if and only if isChecked is false, that is the CheckBox is unchecked.
Let value of the TextInput be the creditCardName if the CheckBox is checked and set it to the userName otherwise.
This code should work (run the snippet for demonstration)
const checkbox = document.getElementById('checkBox')
var input = document.createElement('input')
var submit = document.querySelector("#submit")
var name = "Tom"
checkbox.addEventListener('change', (event) => {
if (event.currentTarget.checked) {
document.querySelector("body").appendChild(input)
input.value = name
} else {
input.parentNode.removeChild(input)
input.value = ""
}
})
submit.onclick = function() {
console.log(input.value)
}
<input type="checkbox" id="checkBox">
<button id="submit">Submit</button>
I am trying to handle multiple checkboxes as follows:
sendFileNameToBackEnd = (filename: string[]) => {
console.log(filename)
this.vizsualizaForFileName(filename)
}
render() {
const handleChange = (checked: boolean, event: React.FormEvent<HTMLInputElement>) => {
let listoffilename : Array<string> = []
const target = event.currentTarget;
const name = target.name;
if(checked && name !== '')
{
listoffilename.push(name)
this.setState({
listoffilename1: listoffilename
});
}
console.log(this.state.listoffilename1)
};
return (
<DataList aria-label="Checkbox and action data list example" isCompact >
{ this.state.fileListData ?
this.state.fileListData.map((fd) =>
<DataListItem aria-labelledby="check-action-item1">
<DataListItemRow>
<DataListCheck
id="controlled-check"
aria-labelledby="check-action-item"
isChecked={isChecked1}
onChange={handleChange}
name={fd}/>
<DataListItemCells
dataListCells={[
<DataListCell key="primary content">
<span id="check-action-item1">{fd ? fd : "Loading..."}</span>
</DataListCell>
]}
/>
</DataListItemRow>
</DataListItem>
):
<>
<EmptyState>
<Title headingLevel="h4" size="lg">Empty state</Title>
</EmptyState>
</>
}
</DataList>
<Button variant="primary" onClick={ () => {this.sendFileNameToBackEnd(this.state.listoffilename1)}}>Visualize</Button>
);
}
In the above code, when I select any checkbox, I can get its value, but when I choose multiple checkboxes I only getting one value in an array.
In 'this.state.fileListData' I have n number of data and I am displaying a checkbox using a map and one button called 'Visualize'. When we select some of the values from the checkbox and click on the 'Visualize' button, I want to send all selected values to the backend for another operation.
When I select multiple checkboxes then it only sends one value in it not all selected values.
Guide me if I am doing something wrong.
I'm trying to make a input box component that has instant feedback using Formik. I want the input box to turn green when the user input matches a predefined string (the "answer"), gray if the input matches the prefix of the answer (including the empty string) and red otherwise. This string is stored as a property of the initial values, values.answer. The Formik validate function checks if the input equals values.answer and sets values.correct = true. I then created a css class corresponding to a green input box and set the className of the input conditional on the value of values.correct. The problem is it only seems to update (i.e turn green with a correct input) when I click out of focus of the input box (i.e onBlur). I would like it to work onChange. How would I do this?
Here is the relevant code sandbox: https://codesandbox.io/s/instant-feedback-box-lub0g?file=/src/Frame.js
Cool problem, but you've overcomplicated your code a little bit 😉 Some feedback:
touched is set to true during onBlur by default. You can override this by using setTouched(), but I found it simpler to just use values instead of touched in your form
try to keep values as minimal as possible, it's only meant to access input values so there's no need for hint and answer to be assigned to it
the purpose of the validation function is to return an errors object and not to set values, so remove assignments like values.correct = true
You don't need to store isDisabled in state, you can derive it from formik.submitCount and formik.isSubmitting
const Note = () => {
const [showFrame, setShowFrame] = useState({ 1: true });
const onCorrectSubmission = (frameId) => {
setShowFrame({ ...showFrame, [frameId]: true });
};
const text =
"What is the sum of the first three natural numbers? (give answer as a word, i.e one, two etc.)";
const hint = "The first three natural numbers are 1, 2, and 3";
const answer = "six";
return (
<div>
<h1>Induction</h1>
{showFrame[1] ? (
<Frame
id={1}
text={text}
hint={hint}
answer={answer}
onCorrectSubmission={onCorrectSubmission}
/>
) : null}
{showFrame[2] ? (
<Frame
id={2}
text={text}
hint={hint}
answer={answer}
onCorrectSubmission={onCorrectSubmission}
/>
) : null}
</div>
);
};
const Frame = ({
id,
text,
hint,
answer,
values,
onCorrectSubmission,
...props
}) => {
const validate = (values) => {
const errors = {};
if (!answer.startsWith(values.cloze)) {
errors.cloze = hint;
} else if (values.cloze !== answer) {
errors.cloze = true;
}
return errors;
};
const formik = useFormik({
initialValues: {
cloze: ""
},
validate,
onSubmit: (values) => {
onCorrectSubmission(id + 1);
}
});
const isFinished = formik.isSubmitting || formik.submitCount > 0;
return (
<form enablereinitialize={true} onSubmit={formik.handleSubmit}>
<p>{text}</p>
<input
id="cloze"
name="cloze"
type="text"
autoComplete="off"
{...formik.getFieldProps("cloze")}
disabled={isFinished}
className={`input
${!answer.startsWith(formik.values.cloze) ? "invalid-input" : ""}
${formik.values.cloze && !formik.errors.cloze ? "valid-input" : ""}
`}
/>
{formik.values.cloze && formik.errors.cloze ? (
<div>{formik.errors.cloze}</div>
) : null}
<button disabled={!!formik.errors.cloze || isFinished} type="submit">
Submit
</button>
</form>
);
};
export default Frame;
Live Demo
I tried to implement something like a multi-select, where the user can either select a value from a data list or can type in a new value. A chosen value should be added to an array if the user presses enter. For detecting changes in the input field I use onChange and a state variable that saves the current value typed in. For detecting the press of enter I use onKeyDown. The problem is that I'm no longer able to type something in the input field, however choosing values from the data list works. I figured out that when I comment out onKeyDown, I can type something in the input field and can also choose from values provided by the data list. However, in this case, adding values to an array on the press of enter doesn't work. I'm fairly new to React, is there something I miss?
My current code looks like follows:
const EditableMultiSelect = ({ field, helpers, metadataField, editMode, setEditMode }) => {
const { t } = useTranslation();
const [inputValue, setInputValue] = useState('');
const handleChange = e => {
const itemValue = e.target.value;
setInputValue(itemValue);
}
const handleKeyDown = event => {
event.preventDefault();
if (event.keyCode === 13) {
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
const removeItem = () => {
console.log('to be implemented');
}
return (
editMode ? (
<>
<div
onBlur={() => setEditMode(false)}
ref={childRef}>
<input name="inputValue"
value={inputValue}
type="text"
onKeyDown={e => handleKeyDown(e)}
onChange={e => handleChange(e)}
placeholder={t('EDITABLE.MULTI.PLACEHOLDER')}
list="data-list"
/>
<datalist id="data-list">
{metadataField.collection.map((item, key) => (
<option key={key}>{t(item.value)}</option>
))}
</datalist>
</div>
{(field.value instanceof Array && field.value.length !== 0) ? (field.value.map((item, key) => (
<span className="ng-multi-value"
key={key}>
{t(item)}
<a onClick={() => removeItem(key)}>
<i className="fa fa-times" />
</a>
</span>
))) : null}
</>
) : (
<div onClick={() => setEditMode(true)}>
{(field.value instanceof Array && field.value.length !== 0) ? (
<ul>
{field.value.map((item, key) => (
<li key={key}>
<span>{item}</span>
</li>
))}
</ul>
) : (
<span className="editable preserve-newlines">
{""}
</span>
)}
<i className="edit fa fa-pencil-square"/>
</div>
)
);
};
You're calling event.preventDefault() every time a key is pressed. You should move it inside the if statement:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
you can't type anything anymore in the input text because in the handleKeyDown event handler, you're calling event.preventDefault() in the early lines. So i think you just have to move it into the if case:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
Remove e.preventDefault() or put it inside the if statements.
It is the one preventing the input from being editable.
I'm trying to prevent my TextInput from getting values like $,%,^,&,(,) etc. Basically my TextInput should allow letters only. My approach is as follows. But still i'm able to input these other characters. How can i prevent special characters from the TextInput
restrict(event) {
const regex = new RegExp("^[a-zA-Z]+$");
const key = String.fromCharCode(!event.charCode ? event.which : event.charCode);
if (!regex.test(key)) {
event.preventDefault();
return false;
}
}
<TextInput
underlineColorAndroid='transparent'
allowFontScaling={false}
style={styles.questionText}
onKeyPress={e => this.restrict(e)}
value={firstNameState}
/>
the onKeyPress event on android does not work very well.
That is why I have chosen to use a method that eliminates these characters and then save it wherever you want, just as it might change the state of your field.
restrict = text => text.replace(/[`~0-9!##$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '')
I have to block special characters by this line of code.
var format = /[!##$%^&*()_+-=[]{};':"\|,.<>/?]+/;
if(format.test(string)){ }
You may define your OnChange event handler using your regex, where you will check if the input string matches your regex with /^[^!-\/:-#\[-`{-~]+$/.test(text):
const {useState} = React;
const App = () => {
const [value, setValue] = useState("");
const onChange = e => {
const input = e.currentTarget.value;
if (/^[^!-\/:-#\[-`{-~]+$/.test(input) || input === "") {
setValue(input);
}
};
return (
<div className="App">
<input
value={value}
onChange={onChange}
underlineColorAndroid='transparent'
allowFontScaling={false}
/>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>