Formik: prevent touching fields (do not mark as .touched) on Submit - reactjs

I have a situation where I need to prevent Formik from marking as .touched my fields when I Submit. .touched should be set when I touch a field, but clicking Submit should reset .touched to {}. I read in the docs that
Before submitting a form, Formik touches all fields so that all errors
that may have been hidden will now be visible.
I have my own custom isInvalid= definition and my own submitClicked variable that I track myself, so I need to turn off or reverse this behavior. I want errors populated, but I want touched to be empty (reset) after clicking Submit.
I was thinking of calling setTouched({}) somewhere, but I need an event that tells me validation has completed. I also don't know where to insert it; I can't call it from a custom useEffect that watches submitClicked.
<Button type="submit"
onClick{() => {
// my own var.
setSubmitClicked(true);
// If I call setTouched({}) here, it does nothing. It gets overridden after Form Submit
setTouched({});
}} Submit
</Button>
also, can't really do it in a useEffect, I don't have access to Formik here and it's a mess to implement,
useEffect(() => {
someFormikContext.setFieldTouched({}); // hard to get Formik Context here
}, [submitClicked]);
Are there any simpler solutions?

You can reset form on submit ,
onSubmit={(values, { resetForm }) => {
// do your stuff
resetForm();
}}
This will reset your form errors / touched , hope this will help you

Related

useForm in React: formState.isDirty is true, but .dirtyFields is empty

I have a React JS form using useForm library.
This is a popular library so I'm not sure this can be a bug but I'm getting into a state which seems incorrect. See the screenshot of the console in chrome below:
How can I have formState.isDirty, but no formState.dirtyFields?
It's a large, complex form but the isDirty state is achieved by human interaction with the form, with registration looking like this:
<input id="value" {...register("value")} defaultValue={variable.value} />
One other observation - the behavior seems to change when observed. For example, when adding this code to the form - it seems to work more as expected:
<div>
{formState.isDirty ? "DIRTY" : "CLEAN"}
<pre>dirty {JSON.stringify(formState.dirtyFields, null, 2)}</pre>
<pre>touched {JSON.stringify(formState.touchedFields, null, 2)}</pre>
</div>
It seems obvious now, but the problem was I had a dependency on formState in a function called on button click. Because clicking the button doesn't cause a re-render of the form, the fresh value of formState is not retrieved from useForm - meaning it is stale.
The trick is to have this change trigger a re-render. You can do this by having it in rendered in the output (so .tsx/.jsx spots the dependency) or by using useCallback to create the save function and register formState in the dependencies of that function, a la
const save = useCallback(() => {
// save using formState dirtyFields, touchedFields etc
}, [formState]);
// ...
<button onClick={save} />
Now, it works great and this explains the behavior change when showing the dirty state in the HTML output.

Conditionally enabling a form button with react-hook-form and material ui

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.

react-hook-form reset errors messages only

I have some dynamic fields, which gets removed/added on the basis of some hook state. I have fields which gets removed from the list but the errors for them are still visible. I have tried to clearErrors, unregister to remove it but nothing works.
is it possible? reset does work but it resets the whole form too.
I am using v6 of react-hook-form and i cannot upgrade it to 7. That's out of the picture for now.
yup validator is being used with it for validations.
I stuck into the same problem seems like bug, if you try unregister the control it doesn't do it. Here how I have done.
When you remove the control do unregister and reset specific control.
const handleRemoveRow = (control) => {
//all code logic and stuff
//................
unregister(control);
reset({ [control]: undefined });
};
After that on useEffect hook assume you have one main state of form, reassign the values back.
useEffect(() => {
const keyValue = getValues();
keyValues.map(({controlName,Value}) => {
setValue(controlName, Value);
});
}, [getValues()]);
This is a more of pseudo-code but I hope you got the concept.

Why won't my app render when state changes?

I'm building an application that allows for posting community concerns with the ability to upvote those concerns using React, and right now, I am working on the upvote functionality. One way I'm trying to limit the currently logged in user to a single vote is to disable the button once an upvote has been successfully registered for any given post.
To do this, I created a function that checks if the logged in user's ID matches the ID of the upvote for each post. If no match is found, this means the user hasn't voted for already and can register the new upvote. Once this is complete, the button is disabled. I created state for this and is set to false upon the initial render (not sure if this is what I should be doing). I also created state for the all of the votes that have been successfully registered. Both are included below.
const [alreadyVoted, setAlreadyVoted] = useState(false);
const [userVotes, setUserVotes] = useState(upvotes) // upvotes is being passed via props
I'm using the useEffect hook (again, not sure if this is the best way) to check if each button should be enabled or disabled like so:
useEffect(() => {
hasVoted()
}, [userVotes])
Finally, my hasVoted function checks to see if the user has already voted for the issue before and determines the state. It looks like:
function hasVoted() {
userVotes.forEach(vote => {
if (vote.issue_id === issue.id) { // issue here is from props
setAlreadyVoted(true) // I want this to then disable my button
}
})
}
Right now, when I click the button to register the upvote, the page doesn't rerender upon clicking. However, if I refresh the page, the button is successfully disabled as it should be. It probably goes without saying, I'm still getting the hang of React, but any and all help is appreciated.
This code is meaningless at first place
userVotes.forEach(vote => {
if (vote.issue_id === issue.id) { // issue here is from props
setAlreadyVoted(true) // I want this to then disable my button
}
})
, it can be transformed into
if (userVotes.some(vote => vote.issue_id === issue.id)) setAlreadyVoted(true)
but still the logic of it is not clear
I think hasVoted function should be in the useEffect scope. If it's outside, it might introduce bugs, as documented here: https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
You could try if this helps to resolve your problem
you seem to be somewhat on the right track here but your problem may lay elsewhere,provided the props are passed correctly,because the application does re-render when the state changes. if you want to just disable button you can just use simple html attribute to do that
{alreadyVoted ? (
<button class="btn btn-success" disabled="disabled">
Upvote
</button>
) : (
<button
class="btn btn-success"
onClick={() =>
// Add the id to the setUserVotes here
setAlreadyVoted(true)}
>
Upvote
</button>
)}
Take a look at this codesandbox that i just created and get back to me if you have any issues still

SetValues Formik Function disables Formik HandleSubmit

I have a form that can handle submission a couple of different ways. The difference is managed by a flag in the form Values that can either be true or false.
I am running into this really weird issue where when I use the Formik setValues() function the form doesn't enter the handleSubmit function at all. It just stops execution. However if I set the value using just by going this.props.values.x = ...
it enters the function and continues with submitting the form just fine.
Why is this happening?
there's really no point in showing code as the description tells you everything but this is what the submit handler looks like:
A confirmation function calls the submit handler which sets the value and then attempts to call handleSubmit
<Confirmation
items={this.confirmationData()}
isLoading={this.props.isSubmitting}
open={isConfirming}
preapproval={true}
submitAnother={this.submitAnother}
onClick={this.submit} //this is the submit handler
onClose={() => this.setState({ isConfirming: false })}
/>
submit = () => {
this.props.setValues({ ...this.props.values, submit: true})
this.props.handleSubmit()
}
if the first line is changed to this.props.values.submit = true, the form submits however, using the setValues function, The application stops executing after setting the submit value to be true.
Turns out the issue was the is Validating Flag being set to true when you call setFieldValue so the simplest solution is to pass a third argument to setFieldValue to manually turn off validation:
submit = () => {
this.props.setFieldValue('submit', true, false)
this.props.handleSubmit()
}

Resources