Check if the form is updated in React JS - javascript

I have a form in my react application which is inside a modal pop up. so when the user closes the pop up i want to check if there is any changes made in the form fields.If so i will show a confirmation modal and if no changes i will close the pop up.
<FormProvider {...methods}>
<form onSubmit={handleSubmit(submitDetails)}>
<FormInput
onChange={handleChange(onChange, value)}
onBlur={onBlur}
value={value ?? ""}
name="name"
/>
<FormInput
onChange={handleChange(onChange, value)}
onBlur={onBlur}
value={value ?? ""}
name="age"
/>
<section className="submit-section">
<Button onClick={() => checkUnsavedAndClose()}>Cancel</Button>
<Button type="submit" disabled={!isValid}>
Save
</Button>
</section>
</form>
</FormProvider>
const checkUnsavedAndClose = () => {
// if the form is not updated
closePopUp();
// if the form is updated
alertPopup();
}
What is the best approach to validate the entire form irrespective of number of fields in it. i was thinking of checking whether the form is touched or dirty. but i am not sure that it would be the best solution. any inputs will be highly appreciated

Assuming the "handleChange" function is used for all inputs one thing you could do is use it to hold on state a variable for changes
handleChange(onChange, value) {
value !== originalValue && setState({anyChanges: true}) //or useState and set the state inside the modal
onChange(value)
}
you could also hold which fields were modified and not save to improve the pop-up message

by creating a boolean changed state. that you can change true/false. And you check the value when you try to close the popup.
const [changed, setChanged] = useState(false);
const handleChange() {
// your code
setChanged(true)
}
const checkUnsavedAndClose = () => {
if(changed) {
alertPopup();
} else {
closePopUp();
setChanged(false)
}

Related

how to set usestate value in ternery operator pass a callback functoin setState()

<label htmlFor="rank" className="font-bbold">Rank:
</label>
<InputNumber
id="rank"
value={singlepoints.rank}
onValueChange={(e) =>
onRankChange(e, index)}
required>
</InputNumber>
{
// (singlepoints.rank === 0 || singlepoints.rank === null ) ? () => console.log('fjdkfhdfhd') : null
// ||
(singlepoints.rank === 0 ||
singlepoints.rank === null ) ?
**(() => setInvalid(true))** &&
<small className="p-error ml-6">
Rank is required.</small>
: null
}
Hi this is not how you handle state change.
First to validate something you usually have onBlur event (it fires when the input looses focus)
Second instead of trying to running code in ternary you have to do it in the useEffect hook:
useEffect(() => {
if (singlepoints.rank === 0 ||
singlepoints.rank === null )
setInvalid(true)
}, [singlepoints])
However I can recommend use formik and yup to do the validation, once you figure it out it will make your life much and much easier in terms of form validation and change handling
You're off to a good start here.
I see that you have a label and an InputName component, and it looks like the "rank" must be required and not zero.
I want to start by making a reference to React "controlled component" which essentially is "an input form element (for example <input />) whose value is controlled by React".
The code below will give you an idea of how to rewrite your code (please note that I added a submit button to handle the conditional statement):
import { useState } from "react";
function App() {
const [singlePointsRank, setSinglePointsRank] = useState("");
const [invalid, setInvalid] = useState(false);
function handleSubmit(e) {
e.preventDefault();
if (singlePointsRank == 0) {
setInvalid(true);
} else {
//do something else if the user provides a valid answer
}
}
return (
<div className="App">
<form>
<label>Rank:</label>
<input
required
name="rank"
value={singlePointsRank}
onChange={(e) => setSinglePointsRank(e.target.value)}
/>
<button onClick={handleSubmit} type="submit">
Submit
</button>
</form>
{/*The error message below will only be displayed if invalid is set to true */}
{invalid && <p>Please provide a valid rank number.</p>}
</div>
);
}
export default App;
Note that the required property in the input element prevents the form from being submitted if it is left blank. Therefore, you do not really need to check for singlePointsRank === null.
This way React will update the state variable singlePointsRank as the user types something in it. Then, when the user clicks on the submit button, React will call the handleSubmit function to which you can add your conditional statement set your second state variable invalid to false.
Regarding ternary operator, I do not recommend using it in this case since you might want to add some extra code to your conditional statement, such as re-setting invalid back to false, removing the error message and clearing the input field to allow the user to submit another rank number.
See the example below just to give you an idea:
import { useState } from "react";
function Random2() {
const [singlePointsRank, setSinglePointsRank] = useState("");
const [invalid, setInvalid] = useState(false);
function handleSubmit(e) {
e.preventDefault();
if(singlePointsRank == 0) {
setInvalid(true);
setSinglePointsRank("");
} else {
// do something else and clear the input field and reset invalid back to false
setInvalid(false);
setSinglePointsRank("");
}
}
function handleInputChange(e) {
if(setSinglePointsRank === "") {
setInvalid(false);
}
setSinglePointsRank(e.target.value)
}
return (
<div className="App">
<form>
<label>Rank:</label>
<input
required
name="rank"
value={singlePointsRank}
onChange={handleInputChange}
/>
<button onClick={handleSubmit} type="submit">
Submit
</button>
</form>
{/*The error message below will only be displayed if invalid is set to true */}
{invalid && <p>Please provide a valid rank number.</p>}
</div>
);
}
export default Random2;
Again, since the main question here is regarding ternary operators and setting state, although I do not recommend using it in this case, you could rewrite the initial handleSubmit function as follows just to play around with your code:
function handleSubmit(e) {
e.preventDefault();
singlePointsRank == 0 ? setInvalid(true) : console.log("do something else");
}
To get a little more practice, you could rewrite
{invalid && <p>Please provide a valid rank number.</p>}
as
{invalid ? <p>Please provide a valid rank number.</p> : ""}

In React how would I disable onSubmit call for some text fields and not others?

I have a component dataModal that has two text boxes, a searchable data table, and a submit button. The Name and Date text boxes need to be filled out manually, and the dataTable is a component that can be searched via it's own text boxes to narrow down what data needs to be entered.
The problem that I am facing is when searching the data table using the text input fields pressing enter causes the form to submit. This is likely happening because the data table is in the form that the submit button is in.
Is there a way to have the text fields in the dataTable not submit the form while retaining the ability to submit the form when pressing enter in the name or date form?
const dataModal = (props) => {
const { data, save, close } = props;
const [name, setName] = useState();
const [date, setDate] = useState();
const handleSubmit = (e) => {
if (e) e.preventDefault();
save(name, date); // DB Call from parent
close(); // Closes the modal
}
return (
<form onSubmit={handleSubmit}>
<customInput
name="NameTextField"
captureInput={(value) => {setName(value)}}
/> // Custom text box component
<customInput
name="DateTextField"
captureInput={(value) => {setDate(value)}}
/> // Custom text box component
<searchableDataTable
name="SearchableDataTable"
data=data
/> // Data Table with customInputs build the same as above that are used for filtering the data table
<Button type="submit" /> // Submission button
</form>
)
}
Moving the data table below the form tabs works, but it causes "ugly" rendering in which the submit button is in the middle of the modal, and not at the bottom. This is the fallback solution if I'm unable to figure this out.
let's assume you pick "Name" to be required (also assuming everything works in your custominput), you could try this:
const [buttonState, setButtonState] = useState();
const isValid = (value) => {
// return true or false
}
...
<customInput
name="NameTextField"
captureInput={(value) => {
setName(value)
setButtonState(isValid(value))
}}
/>
...
<Button {buttonState ? disabled="disabled" : ""} />
Please note isherwood's comment also.

Can value of input box be set to to empty string on clicking submit button when the input is in a stateless functional component?

How to clear the value inside the input in function Admin after I click the "Add" button? Should i use another class based component instead of a functional component?
I have set the value of one of the input box as : value={props.item} and in the this.setState I update the value of item as item:"".
AddInfo(info){
let s = this.state.products;
let obj ={name:""};
obj.name=info.productName;
s.push(obj);
this.setState({
products:s,
item:"" //Here i set the value of item equal to an empty string.
})
console.log(this.state.products);
}
function Admin(props){
let productName="";
return (
<div>
<input type="text" required placeholder="Product Name" onChange={(e)=>{productName=e.target.value}} value={props.item}></input><br/>
<button type="Submit" onClick{(e)=>props.AddInfo({productName})}>Add</button>
</div>
)
}
You have to save your input within a local state of the input function:
AddInfo(info){
let s = this.state.products;
let obj ={name:""};
obj.name=info.productName;
s.push(obj);
this.setState({
products:s,
})
console.log(this.state.products);
}
function Admin(props){
const [productName, setProductName] = useState('');
return (
<div>
<input type="text" required placeholder="Product Name" onChange={(e)=> setProductName(e.target.value) value={productName}></input><br/>
<button type="Submit" onClick{(e)=> {props.AddInfo({productName}); setProductName('')}}>Add</button>
</div>
)
}
This will work for you, since you are not mutating the productName variable anymore but now you are saving it in a local state of that input function.
Hope this helps!
Admin is like a form, and the main decision you have to make is rather you want it to be controlled (info is stored in stated, and state is reflected in the ui), or uncontrolled (info is taken from the dom once 'Add' is clicked.
Since you want to empty the input once 'Add' is clicked it makes sense to make this component controlled.
The next decision is rather you want it to be a functional component, or a class component. In nowadays it doesn't really matter (functional components can now use state with the state hook).
To store state in you functional component use React's useState hook.
function Admin({addInfo}){
const [productName, setProductName] = useState(")
return (
<div>
<input
type="text"
placeholder="Product Name"
onChange={(e)=>{
setProductName(e.target.value)
}
value={prodcutName}>
</input>
<button
onClick{(e)=>{
props.addInfo({productName})
setProductName("") // Will set the input to an empty string
}
>
Add
</button>
</div>
)
}

Input validation - react form submitting without any values

I'm having an input validation problem thats allowing the form to submit without having any selectorValues added. The check I have seems to only check for input inside the textarea but doesn't account for the Add button being pressed.
Here's a sandbox reproducing the issue.
I'm using Semantic-ui-react so my <Form.Field /> looks like this:
<Form.Field required>
<label>Selector Values:</label>
<TextArea
type="text"
placeholder="Enter selector values 1-by-1 or as a comma seperated list."
value={this.state.selectorValue}
onChange={this.handleSelectorValueChange}
required={!this.state.selectorValues.length}
/>
<Button positive fluid onClick={this.addSelectorValue}>
Add
</Button>
<ul>
{this.state.selectorValues.map((value, index) => {
return (
<Card>
<Card.Content>
{value}
<Button
size="mini"
compact
floated="right"
basic
color="red"
onClick={this.removeSelectorValue.bind(this, index)}
>
X
</Button>
</Card.Content>
</Card>
);
})}
</ul>
</Form.Field>
So in the above, <TextArea> has a required prop: !this.state.selectorValues.length. This is only checking for input inside the textarea, it should check that the value has been added by pressing the Add button before allowing the form to submit.
In your addSelectorValue add a check to see if this.state.selectorValue it not empty, if it is just return, this will prevent adding empty values to selectorValues
addSelectorValue = e => {
e.stopPropagation();
e.preventDefault();
if (!this.state.selectorValue) return;
//continue if this.state.selectorValue has a value
};
Before submitting add a check to see if this.selectorValues is empty, if so focus on textarea.
To focus we need to first create a ref to our textarea.
Create a ref to be
attached to a dom element
textareaRef = React.createRef();
// will use our ref to focus the element
focusTextarea = () => {
this.textareaRef.current.focus();
}
handleSubmit = () => {
const { selectorValues } = this.state;
if (!selectorValues.length) {
// call our focusTextarea function when selectorValues is empty
this.focusTextarea();
return;
}
this.setState({ submittedSelectorValues: selectorValues });
};
// attach our ref to Textarea
<Textarea ref={this.textareaRef} />
After some search ... required prop is for decorational purposes only - adding astrisk to field label.
It has nothing to form validation. You need a separate solution for that - try formik or set some condition within submit handler.
Formik plays nicely with yup validation schema - suitable for more complex, dynamic requirements.

Redux Form: Conditional form validations with multiple submit buttons

I am using Redux forms with multiple submit buttons along with redux-form <Field />. I need to control the validation of these fields based on click of these buttons. For ex. I need to set a flag to True/False based on click of these buttons so that I can conditionally validate my fields like below:
<Field
name="date"
component={DateFormField}
validate={isSaveDraft && [validateRequiredText]}
floatingLabelText="Date"
fullWidth
helpText="blablabla"
/>
<Field
name="title"
component={TextFormField}
normalizeOnBlur={normalizeTextOnBlur}
validate={!isSaveDraft && [validateRequiredText]}
floatingLabelText="Project title"
fullWidth
helpText="blablabla"
/>
As you can see from above code, I'm conditionally validating my fields with validate={isSaveDraft && [validateRequiredText]} and validate={!isSaveDraft && [validateRequiredText]}
Here are my two submit buttons:
<RaisedButton
label={submitting ? 'Saving Draft...' : 'Save Draft'}
type="button"
onClick={handleSubmit(values => onSubmit())}
disabled={submitting}
primary
/>
<RaisedButton
label={submitting ? 'Submitting Brief...' : 'Submit Brief'}
type="button"
onClick={handleSubmit(values => onSubmit())}
disabled={submitting}
primary
/>
However, I'm not able to achieve it. Please help.
After a lot of headaches and head scratching, I've finally found the solution without making things look ugly. Thanks to a fellow developer for this solution. Original idea was to set a flag on click of the button and conditionally validate the fields. (PS: I'm using Field Level Validations). However, the issue was that the validations are being executed BEFORE the flag setting as the onClick handler wouldn't fire before all the validations are fixed and that logic is buried deep inside redux-forms (perks of unnecessarily overcomplicating simple things by using a library).
Here is the solution:
submit handler
handleSubmit() {
const { bookingCreate, modalShow, navigateTo } = this.props;
const { isDraftAction } = this.state; // my flag
// create record!
return bookingCreate(isDraftAction)
.then(responsePayload => {
...
})
.catch(handleSubmissionError);
}
isDraftAction is the flag which is set (in local state) when the action is called on onClick of both buttons.
My conditional Field level validations
<Field
name="date"
component={DateFormField}
validate={isDraftAction && [validateRequiredText]}
fullWidth
helpText="blablabla"
/>
<Field
name="title"
component={TextFormField}
normalizeOnBlur={normalizeTextOnBlur}
validate={!isDraftAction && [validateRequiredText]}
fullWidth
helpText="blablabla"
/>
My 2 buttons for SAVE RECORD and SUBMIT RECORD.
const submit = handleSubmit(values => onSubmit()); // this is redux-form's submit handler which will in-turn call my own submit handler defined above. (That's where the library hides all the logic and makes developer helpless)
<RaisedButton
label={submitting && isDraft ? 'Saving Draft...' : 'Save Draft'}
type="button"
onClick={() => {
this.props.dispatchAction({ draftAction: true }).then(() => {
submit();
});
}}
disabled={submitting}
primary
/>
<RaisedButton
label={submitting && !isDraft ? 'Submitting Brief...' : 'Submit Brief'}
type="button"
onClick={() => {
this.props.dispatchAction({ draftAction: false }).then(() => {
submit();
});
}}
disabled={submitting}
primary
/>
dispatchAction() is my action function which will FIRST set the flag to true/false THEN call redux-forms inbuilt submit handler. Also, I've extracted the redux-form's submit handler as it is above only for more clarity.
dispatchAction()
dispatchAction={({ draftAction }) =>
new Promise(resolve => {
this.setState({ isDraftAction: draftAction }, resolve);
})
}
use a state variable as isSaveDraft and set it default to false
Add onClick action of button to set state.isSaveDraft = true
validate={(isSaveDraft && [validateRequiredText]} will not work since validate expects a function all the time. Also, field level validation happens onTouch, or onBlur etc. which is way too early as you won't know which button will be clicked.
What you need to do is use a extra flag prop to hold which button was clicked and then use that in the form level validation.
So your Fields will look like this (no more Field level validation):
<Field
name="date"
component={DateFormField}
floatingLabelText="Date"
fullWidth
helpText="blablabla"
/>
...
<RaisedButton
label={submitting ? 'Saving Draft...' : 'Save Draft'}
onMouseDown={() => this.setDraftFlag(true)}
...
/>
<RaisedButton
label={submitting ? 'Submitting Brief...' : 'Submit Brief'}
onMouseDown={() => this.setDraftFlag(false)}
...
/>
I'm using onMouseDown to capture the flag since you're already using onClick for submitting the form. Besides, we need to do the validation before submit anyway.
And then in your form's validate (based on SyncValidation example)
const validate = values => {
const errors = {}
const {isDraft, ...rest} = values
if(isDraft) {
// draft validations
} else {
// other validations
}
return errors
}
export default reduxForm({
form: 'myForm', // a unique identifier for this form
validate, // <--- validation function given to redux-form
})(MyForm)
setDraftFlag can be an action or you can make it part of your Form component like so (I find this easier since you already get change bound to your form):
setDraftFlag(value) {
this.props.change('isDraft', value));
}

Categories