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));
}
Related
<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> : ""}
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)
}
For normal html elements you can get the value of that element through a form.
function handleSubmit(event) {
event.preventDefault()
const formData = new FormData(e.target)
console.log(formData.get("test"))
// logs the value of the html element with the name "test"
}
<form onSubmit={handleSubmit}>
<input name="test" />
<button type="submit">Submit</button>
</form>
The form will assign a key "test" the value of whatever is inside the input on the form's submission.
My question: Is there a way to get this functionality to work with a Semantic UI Dropdown?
Below is ideally what I would think would work but I know doesn't because the component is a wrapper for other elements.
function handleSubmit(event) {
event.preventDefault()
const formData = new FormData(e.target)
console.log(formData.get("test"))
// logs the value of the html element with the name "test"
}
<form onSubmit={handleSubmit}>
<Dropdown name="test" selection options={
[{key: '1', text: 'text', value: '1'}]
}/>
<button type="submit">Submit</button>
</form>
On form submit, whatever value is selected in that dropdown is put into a key called "test" in formData. If no value is selected, an undefined value is given to the key "test."
Is this possible? I have another work around using a basic html selector but would like to not change a lot of already written code.
Yes, but you will need to deconstruct the dropdown component.
const [open, setOpen] = useState(false)
<Dropdown
trigger={<Button onClick={() => setOpen(true)}>Open</Button>}
onClose={() => setOpen(false)}
icon={null}
open={open}
simple
>
<Dropdown.Menu>
... add the options with the "name" tag ...
</Dropdown.Menu>
</Dropdown>
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.
With the following method:
handleClick(event) {
const inputText = this.refs.inputText
console.log(inputText.value.trim())
}
I am trying to get Material-UI's <TextField/> to return the input text correctly with ref like the <input/> can with <button/> triggering it:
<input
className='form-control'
placeholder='Input Text'
ref='inputText'
type='text'
/>
<button
onClick={(event) => this.handleClick(event)}
>
And I attempted the following with <TextField/>, but it returns as undefined. How can I get it to return inputted text correctly like the <input/> above?
<TextField
hint='Enter text'
className='form-control'
ref='inputText'
type='text'
/>
I would suggest this approach:
Set up your textfield with a value and onChange function that are hooked into redux itself, where the onChange function just updates the value.
So you'd have something like this :
<TextField
value={this.props.textFieldValue}
onChange={this.props.textFieldChange}
Where the textFieldChange is an action that simply updates the textFieldValue. Most forms in redux will work something like this. Keep in mind the names i made up for those props and action are just for example. If you have a big form you might want to consider have part of the state tree dedicated to the form itself where you have :
state: {
form: {
textField: ...your textfield value here,
name: ...,
whateverElse: ...
}
};
I like doing this with redux because I can make that architect form part of the state to look like the json payload of wherever I'm sending it to, so there I can just send the form went I want to send it.
Anyways, back to this example. When you click your handleClick now. All you need to do is this to get the value:
handleClick(event) {
console.log(this.props.textFieldValue.trim());
}
Because the textfield is updated with every change, you always have access to it in your state. This also gives you flexibility over the refs approach, because if you use refs you will have a lot harder of a time getting access to that form in other components. With this approach, all the information is on your state so you can access it anytime, as long as you manage your props.
You should use the onChange={} to get the value:
_onChange = (e) => {
console.log(e.target.value);
}
<TextField
onChange={this._onChange}
/>
Here's a better solution than using onchange event, we get directly the value of the input created by material-ui textField :
create(e) {
e.preventDefault();
let name = this.refs.inputText.input.value;
alert(name);
}
constructor(){
super();
this.create = this.create.bind(this);
}
render() {
return (
<form>
<TextField ref="inputText" hintText="" floatingLabelText="Your name" /><br/>
<RaisedButton label="Create" onClick={this.create} primary={true} />
</form>
)}
hope this helps.