How to clear some fields in form - Redux-Form - reactjs

I'm working on a page which has many input validations and logical bindings on it and every sprint iteration the page size increasing. So that, I have to find a beautiful and scalable solution.
Imagine, when user select a value from dropdown as 'A', some fields must be disabled, some fields must be cleared and some fields initilized with default values. I can change one related field (doesn't have validation rule like regexp or lenght constrait) value with some little code like
this.props.dispatch(change('xForm','xField','xValue' ))
My problem is that when I need to clear multiple fields,
It always blocked by my validation method and clear operation is failed ( Note : I supposed to be like that but not like that)
.
I tried some strategies as below but y,z,w fields have some text and it triggered validation rule and hanled errors. So that, inputs have still old values, not cleared ones.
//Clear
this.props.dispatch(change('xForm','yField','' ))
this.props.dispatch(change('xForm','zField','' ))
this.props.dispatch(change('xForm','wField','' ))
What are the best practises for clear inputs or assign some values to inputs in redux-form for pages which have highly dependent inputs.
I have been researching for 2 days but I couldn't find any optimal solution. (redux normalizer, redux form utils etc.)
Thanks.

This worked for me:
resetAdvancedFilters(){
const fields = ['my','fields','do','reset','properly']
for (var i = 0; i < fields.length; i++) {
this.props.dispatch(change('formName',fields[i],null))
this.props.dispatch(untouch('formName',fields[i]))
}
}

Using the below, it clears the respective multiple fields and also clears the errors if any for those respective fields.
Common function to reset multiple fields.
import {change, untouch} from 'redux-form';
//reset the respective fields's value with the error if any
resetFields = (formName, fieldsObj) => {
Object.keys(fieldsObj).forEach(fieldKey => {
//reset the field's value
this.props.dispatch(change(formName, fieldKey, fieldsObj[fieldKey]));
//reset the field's error
this.props.dispatch(untouch(formName, fieldKey));
});
}
Use the above common function as,
this.resetFields('userDetails', {
firstName: '',
lastName: '',
dateOfBirth: ''
});

Wow, that's some complicated logic. The absolute ideal way would be to use the plugin() API to change values in the reducer when your other field values change. It's a complicated API, because you have total control to mess things up in the reducer, but you have a complicated requirement.
However, dispatching three change() actions like you said you are doing should also work fine. You might want/need to untouch() the fields as well if they have Required validation messages that you don't want to show yet.
It always blocked by my validation method and clear operation is failed.
Can you elaborate on what that means, show a stack trace or console error output?

there.
Let's look at http://redux-form.com/6.0.0-alpha.4/docs/faq/HowToClear.md/
For whole form we can use 4 methods:
A) You can use the plugin() API to teach the redux-form reducer to respond to the action dispatched when your submission succeeds.
B) Simply unmount your form component
C) You can call this.props.resetForm() from inside your form after your submission succeeds.
D) You can dispatch reset() from any connected component

Try to use the dispatch function in meta object with following payload:
meta.dispatch({
type: '##redux-form/CHANGE',
payload: null,
meta: { ...meta, field: name },
})
That should do the trick for any field you want.

I found a better way for this,
We can use initialize action creator of RF.
let resetFields = {
firstName: '',
lastName: '',
dateOfBirth: ''
}
this.props.initialize(resetFields, true); //keepDirty = true;
If the keepDirty parameter is true, the values of currently dirty fields will be retained to avoid overwriting user edits.

clear multiple fields
import {change} from 'redux-form';
const fields = {name: '', address: ''};
const formName = 'myform';
const resetForm = (fields, form) => {
Object.keys(fields).forEach(field => dispatch(change(form, field, fields[field])));
}
resetForm(fields,formName);

If we are talking about entering fields into a form, and then navigating away before submitting, this is the best solution. Place this in your componentDidMount() with the fields you want cleared.
this.props.initialize({
firstName: null,
lastName: null,
bankRoutingNum: null,
newBankType: null
});

For a functional component, you can you change function of RF.
Pass change as props to your component and use it like change(Field_name, value)
For example:
import { reduxForm } from "redux-form";
const Screen = ({
handleSubmit,
change
}) => {
onChange = () => {
change(Field_name, value)
}
return (
// Your code
)}

You can use the clearFields prop or action creator to clear multiple fields.
For example, in this simple form, I use the clearFields prop to clear the firstName and lastName fields.
import React from "react";
import { Field, reduxForm } from "redux-form";
const SimpleForm = (props) => {
const { clearFields } = props;
const clearName = (e) => {
e.preventDefault();
clearFields(false, false, ...["firstName", "lastName"]);
};
return (
<form>
<div>
<label>First Name</label>
<Field name="firstName" component="input" type="text" />
</div>
<div>
<label>Last Name</label>
<Field name="lastName" component="input" type="text" />
</div>
<div>
<label>Occupation</label>
<Field name="occupation" component="input" type="text" />
</div>
<button onClick={clearName}>Clear Name</button>
</form>
);
};
export default reduxForm({
form: "simple"
})(SimpleForm);

Related

How to make an input of type number a controlled component in react

export default function Form() {
const [user, setUser] = useState({
name: "",
numOfQs: 0
})
console.log(user)
function handleUserDataChange(event) {
setUser(prevUser => {
return {
...prevUser,
[event.target.name]: event.target.value
}
})
}
return (
<>
<input
type="text"
placeholder="username"
name="name"
value={user.name}
onChange={handleUserDataChange} />
<input
type="number"
name="numOfQs"
value={user.numOfQs}
onChange={handleUserDataChange} />
</>
)}
I was trying to build my form using react, and when I tried to use input[type: number] on the form field it was giving me this error, don't know why. I was reading through react docs about forms, and everything from the checkbox, radio buttons, textarea was all working fine. but when I used an input element of the type number, I got the following error.
*!Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property target on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist(). See fb.me/react-event-pooling for more information.
so, the problem only arises when an input of type "number" is introduced. when I remove it all of my other form elements work fine.
I'm still in the learning phase of react. please help me out.
This happened because the event that passed into the function is used as an asynchronous event.
To fix this, decompose the event object
function handleUserDataChange(event) {
const { name, value } = event.target;
setUser(prevUser => {
return {
...prevUser,
[name]: value
}
})
}

How do I override the defaultValues in useForm and maintain the isDirty function?

I have a situation where I need to call my DB to obtain some default values. But in the off-chance the data does not exist, I will set some default values in my useForm. Basically, this means that the defaultValues declared in useForm is a fallback value if I fail to obtain the default values from the DB.
From what Im understanding, according to the documentation with regards to useForm,
The defaultValues for inputs are used as the initial value when a component is first rendered, before a user interacts with it.
Or basically, the useForm is one of the first things defined when the page is loaded.
So, unless I can call my DB before useForm is loaded, Im a little stuck on this.
I've read that each Controller field can have something called defaultValue which sounds like the solution, but the documentation mentioned a caveat of
If both defaultValue and defaultValues are set, the value from defaultValues will be used.
I considered setValues but I want to use the isDirty function, which allows field validation and the value used to check whether the form is dirty is based on the useForm defaultValues. Thus, if I were to use setValues, the form would be declared dirty, which is something I do not want.
TL;DR this is what I want:
This is my initial value(the fallback value, result A).
const { formState: { isDirty }, } = useForm(
{defaultValues:{
userID: "",
userName: "",
userClass: "administrator",
}}
);
What I want to do is to make a DB call and replace the data, so that it would now look something like this(result B) if the call is successful(if fail, it will remain as result A).
const { formState: { isDirty }, } = useForm(
{defaultValues:{
userID: "1",
userName: "user",
userClass: "administrator",
}}
);
Please note that the DB call will replace only the userID and userName default values, the default value for userClass will be maintained.
So, the flow is as such:
Case 1: render form -> result A -> DB call -> success -> result B
Case 2: render form -> result A -> DB call -> fail/no data -> result A
So, unless I actually key in an input that is different from the default values of either results depending on the case, both Case 1 and Case 2 should return isDirty==false when I check it.
For react-hook-form#7.22.0 and newer
I think you want to use reset here in combination with useEffect to trigger it when your DB call has finished. You can additionally set some config as the second argument to reset, for example affecting the isDirty state in your case.
Although your answer works there is no need to use getValues here, as reset will only override the fields which you are passing to reset. Your linked answer is for an older version of RHF, where this was necessary.
Also if you're not adding a field dynamically on runtime then you can just pass the whole object you get from your DB call to reset and set the shouldUnregister to true in your useForm config. This way props from your result object which haven't got a corresponding form field will get ignored.
export default function Form() {
const { register, handleSubmit, reset, formState } = useForm({
defaultValues: {
userID: "",
userName: "",
userClass: "administrator"
},
shouldUnregister: true
});
console.log(formState.isDirty);
const onSubmit = (data) => {
console.log(data);
};
const onReset = async () => {
const result = await Promise.resolve({
userID: "123",
userName: "Brian Wilson",
otherField: "bla"
});
reset(result);
};
useEffect(() => {
onReset();
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>User ID</label>
<input type="text" {...register("userID")} />
<label>User Name</label>
<input type="text" {...register("userName")} />
<label>User Class</label>
<input type="text" {...register("userClass")} />
<input type="submit" />
</form>
);
}
Here is a Sandbox demonstrating the explanation above:
For older versions
Just merge your defaultValues or field values via getValues with the result of your DB call.
export default function Form() {
const {
register,
handleSubmit,
reset,
formState,
getValues,
control
} = useForm({
defaultValues: {
userID: "",
userName: "",
userClass: "administrator"
},
shouldUnregister: true
});
console.log(formState.isDirty);
const onSubmit = (data, e) => {
console.log(data);
};
const onReset = async () => {
const result = await delay();
reset({ ...getValues(), ...result });
};
useEffect(() => {
onReset();
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>User ID</label>
<input type="text" {...register("userID")} />
<label>User Name</label>
<input type="text" {...register("userName")} />
<label>User Class</label>
<Controller
name="userClass"
control={control}
render={({ field }) => <input type="text" {...field} />}
/>
<input type="submit" />
</form>
);
}
I went and further googled the issues I had with #knoefel's original answer, and I came across this
Thus, the solution I came up with, based on a combination with #knoefel's answer and the answer in the link:
useEffect(async () => {
let dataArray={};
let result= fetch('my-db-call');
if(result) {
dataArray['userID']=result.userID
dataArray['userName']=result.userName
}
reset({...getValues(), ...dataArray})
}, [])
Apparently, what happens is that the the reset function will first set the values, result A, using ...getValues() and any subsequent data after will replace the previously set values only if it exist. (eg. if my dataArray object lacks the userID key, it will not replace the userID default with the new default. ).
Only after both getValues and dataArray is set then will it reset the default values.
As far as I can tell, this is more or less incline with what I need and should give me result B.

Enzyme change another input field

I have a simple React component which has one email input field and a checkbox like this:
interface MyProps {
onSubmit?: (form: any) => void;
}
class Preferences extends React.Component<MyProps> {
state = {
primaryEmailCheckbox: false,
primaryEmail: "",
};
onPrimaryEmailChange = e => {
this.setState({ primaryEmail: e.target.value });
let checkbox = document.getElementById("primaryEmailCheckId") as HTMLInputElement;
checkbox.disabled = false; //<<< checkbox is null. lets say this is line 18
}
}
render() {
return (
<StaticContent />
<h3>Email Address</h3>
<div className="ui mini icon input">
<input type="email" value={this.state.primaryEmail} placeholder="Enter email..." onChange={this.onPrimaryEmailChange} />
</div>
<div className="ui checkbox">
<input type="checkbox" disabled={true} id="primaryEmailCheckId" onChange={e => this.setState({ primaryEmailCheckbox: e.target.checked })} /><label> Continue to receive further email communications from Us </label>
</div>
);
}
}
export default Preferences;
When anyone enters any thing on the email field, the checkbox becomes visible for user to check it or keep it unchecked.
When I run the application, it works as expected. But when I test it, it says checkbox is null (at line 18), so you cannot set disabled on that.
This is a test to test Preferences Component:
import * as React from "react";
import { shallow } from "enzyme";
import Preferences from "../../components/Preferences";
test("Preferences shows email and checkbox", () => {
const wrapper = shallow(<Preferences onSubmit={() => { }} />);
wrapper.find("input").at(0).simulate("change", {
target: {
value: "a#b.c",
}
});
expect(wrapper.find("input").state().value).toEqual("a#b.c");
});
This throws Null exception at line 18. The thing is, the value a#b.c is passed correctly and I verified it by placing log statements.
But, when I try to change the value of input type email, it calls a onChange method which tries to access (and change) the value of another input field.
I don't know how to change the value of 2nd input type which is a checkbox. How I can I make it work? Any help is appreciated.
This is because the shallow(...) rendering method provides a limited set of interaction patterns and document.getElementById(...) is not one of them. You should be able to get what you want using the following:
const wrapper = mount(<Preferences />, { attachTo: document.body });
(Docs for the above code. You can swap out document.body for the relevant equivalent if you're using something like JSDOM or similar).
That said... using document.getElementById at all is a huge red-flag in React development. Because React lets you interact with a virtual DOM and handles the application of that to the real DOM, fiddling with the real one yourself is a great way to end up with all sorts of bugs. A much better option would be to use refs to access the checkbox in the "React way", or just make checkboxEnabled: boolean part of your state and update it inside your onPrimaryEmailChange() method.

With Redux-Form V7, how to gain access to which fields are currently touched?

I have a form (let's call it the LoginContainer) that has the <Field /> component on it:
import { reduxForm, Field } from 'redux-form'
...
class LoginContainer extends Component {
...
render() {
const { handleSubmit, pristine, submitting } = this.props
return (
...
<Field
component={AuthFormInput}
type="password"
name="person_password"
placeholder="Password"
/>
AuthFormInput.jsx
const AuthFormInput = ({
input,
name,
placeholder,
type,
meta: { touched, error },
}) => {
return [
<div className="row-md" key={`${name}_field`}>
<input
placeholder={placeholder}
name={name}
type={type}
className={maybeHasError}
{...input}
/>
</div>, // error display works amazing here, no problem
<div key={`${name}_error`}>{touched && (error && <span className="edit_required">{error}</span>)}</div>,
]
}
The problem is, I am trying to handle errors outside of the AuthFormInput component, and I can't find any solution that allows me to access the touched state of each field from anywhere other than AuthFormInput, which is abstracted inside <Field />
Is there a way I can get access to meta.touched from inside LoginContainer?
Redux-Form doesn't seem to have any kind of getTouchedFields() function that I can use.
I can collect the currently errored fields easily through the validate function, but I can't find a way to see what fields are touched.
I can't find a way to pass data from AuthFormInput to LoginContainer because <Field /> is between them which is internally controlled by Redux-Form.
Is there any way to do this with Redux-Form V7?
Is there a way I can create a HOC that allows me to track meta.touched inside <Field />?
Ok, I was able to achieve this and it was extremely non-trivial.
Stepping forward from the code in the above question, I added this to LoginContainer:
import withFormListener from './withFormListener'
const listeningAuthFormInput = withFormListener(AuthFormInput)
So, then <Field /> became this:
<Field
component={listeningAuthFormInput}
type="password"
name="person_password"
placeholder="Password"
/>
The HOC wraps around the text input field (or any field you want), and simply listens for changes to the props and then updates Redux:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { updateField } from './auth_actions'
const withFormListener = function listenToFormField(FormField) {
class WrappedField extends Component {
handleFieldUpdate() {
const { name } = this.props.input
const { touched, error } = this.props.meta
if (touched && error) {
if (this.props[`error_${name}`] === error) return null
return this.props.updateField({ prop: name, value: error })
}
if ((touched && !error) && this.props[`error_${name}`].length) {
return this.props.updateField({ prop: name, value: '' })
}
return null
}
componentDidUpdate() {
this.handleFieldUpdate()
}
render() {
return <FormField {...this.props} />
}
}
const mapStateToProps = ({
auth: { error_person_tel, error_person_password },
}) => ({
error_person_tel, error_person_password,
})
return connect(mapStateToProps, { updateField })(WrappedField)
}
export default withFormListener
Logically, what is happening in the HOC is:
this.props inside AuthFormInput becomes this.props inside the HOC
listen to this.props.meta and this.props.input
if the field is touched and has an error, update Redux
if the field is touched and no longer has an error, wipe it out of Redux
You should see my action creator and reducer from LoginContainer so there is no confusion.
Action Creator
export const updateField = ({ prop, value }) => ({
type: AUTH_UPDATE_FIELD,
payload: { prop, value },
})
Reducer
I recommend keeping the state object flat. I experimented with nesting in here, such as:
const INITIAL_STATE = {
errors: { field1, field2 }
}
But, it was giving me significant problems with updating the state, due to equality reasons and complexity with avoiding overwriting errors. It's much nastier than it looks. Therefore, I have settled on this:
const INITIAL_STATE = {
...
error_person_tel: '',
error_person_password: '',
}
case AUTH_UPDATE_FIELD: {
const { prop, value } = action.payload
return {
...state,
[`error_${prop}`]: value,
}
}
You can read more about normalizing state here (ie: flattening):
https://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
I haven't got around to optimizing it at all, but here is how I am displaying the errors currently (inside LoginContainer):
<ServerErrors
errors={[
this.props.error_person_tel,
this.props.error_person_password,
].filter(val => val)}
/>
It is a simple component that renders either null or an array of error strings.
I highly recommend using the built-in Redux-Form field error handling. It works great. I unfortunately had to abstract the control away from the field-level to accomplish a UI design, so this was the only solution in V7.
I searched for weeks. I made another Stack Overflow question and it got no activity, neither did this one. I'm glad I was able to achieve a functioning solution.
I also highly recommend you take a look at Formik. I used it in React Native to accomplish this UI requirement a few weeks ago. You will likely find it works very well. Talk to Jared Palmer on Twitter if you encounter any issues with it. Formik boasts performance gains vs. Redux-Form due to smarter architecture. I am very happy with it in React Native.
If you venture down this road, be extremely careful about setting off infinite loops caused by setState. I did hours and hours and hours of research and trial & error to make this :) I am reasonably happy with the conciseness of the solution. Please let me know if you have a better way.

How to reset initial value in redux form

I'm using redux-form. I'm showing initial values in input fields from the state.
When I click on reset, the input field is still showing initial values.
How can I reset the input field?
This is my code :
const mapStateToProps = (state) => {
return {
initialValues: {
name:state.userInfo.data.name,
email:state.userInfo.data.email,
phone:state.userInfo.data.phone,
city:state.userInfo.data.city.name,
}
};
}
This is how I'm calling initial value in the input field.
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<input className="form-control control_new" {...input} placeholder={label} type={type}/>
{touched && ((error && <span>{error}</span>))}
</div>
)
<Field type="text" {...name} name="name" component={renderField} label="Enter Your Name" />
Thanks
import {reset} from 'redux-form';
...
dispatch(reset('myForm')); // requires form name
Harsha's answer is correct, but you can also call this.props.reset(), which already knows your form name, from inside your decorated form component.
You can see it in action on the "Clear Values" button in the Simple Example.
TLDR: set enableReinitialize: true on your form
I had this same problem recently and came across this post, with none of the answers working. Finally found the solution so hopefully this will help someone else. the problem is those initial values not getting reset when you reset the form. here is a github issue asking about it, and here is the current documentation about it
Upon onClick of the clear button call a different function as follows.
onClick={this.onClearChanges}
Pass the initialValues to the function this way, and as everyone mentioned, Don't forget to set enableReinitialize: true.
onClearChanges = () => {
const { reset, onClearChanges, InitialValues } = this.props;
reset();
onClearChanges(InitialValues);
};
It's also possible to reset inside of your constructor if you don't have a button with a method to call.
I'm using a Drawer Navigator to navigate to different screens(not a clickable button with an onClick method) so I had to reset
constructor(props) {
super(props);
this.props.reset(); //<-- this
}
inside of the first component to handle that specific redux form and then I set
enableReinitialize: true
for that form.

Resources