I have been using react hook form library with native elements but would like to switch to custom components using the Controller API.
I am having an issue with my custom input component updating React state but not updating the ref inside the form state. Thus, a required field is always marked as invalid and I cannot submit my form.
Here is a demo of my issue: https://codesandbox.io/s/react-hook-form-controller-bofv5
It should log out form data upon submission - but submission never happens because form is not valid.
I think I have narrowed down your issue. First I removed the rules={{ required: true }} from the controller and tried the form. It told me firstName: undefined. Then I commented out the onChange attribute. After that, the form is working fine. It seems that onChange should be used if you want to provide a custom value extractor. The value needs to be returned from the function. An example of a simple input would be this: onChange={([{target}]) => target.value} reference. Additionally, it is important to note that handleSubmit extracts some internal state with the values, like that you don't need to keep track of those yourself.
This updated component seems to be working:
function App() {
const { control, handleSubmit, errors } = useForm();
// const [data, setData] = useState({ firstName: "" });
const onSubmit = data => console.log(data);
// const onChangeHandler = e => {
// const { name, value } = e.target;
// const _data = { ...data };
// _data[name] = value;
// setData(_data);
// };
return (
<>
{/* <p>{JSON.stringify(data)}</p> */}
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={Input}
name="firstName"
id="firstName"
label="First Name"
control={control}
// value={data.firstName}
rules={{ required: true }}
errors={errors.firstName}
// onChange={([e]) => onChangeHandler(e)}
/>
<input type="submit" />
</form>
</>
);
}
Just a side note, I've never worked with this library so only trust me as far as you can toss me.
Related
My app gets initialized with a json payload of 'original values' for attributes in a task form. There is a shared state between various components through a context manager submissionState that also gets imported to this form component. This shared state is a copy of the original values json but includes any edits made to attributes in the payload. I would like to include individual 'reset' buttons for each input of the form which would update the shared state to the original value in the json payload. The original values get passed into the parent form component as props and the edited value state gets called from within the parent component as well.
const FormInput = ({ fieldName, fieldValue, onChange, originalValue }) => {
const handleReset = e => {
//????
}
return (
<div>
<label htmlFor={fieldName}>
{fieldName}
</label>
<br />
<input type="text"
name={fieldName}
value={fieldValue}
onChange={onChange}/>
<button id="reset" onClick={handleReset}>↻</button>
<br />
<div>{originalValue}</div>
</div>
);
};
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
function handleChange(evt) {
const value = evt.target.value;
setSubmission({
...submissionState,
[evt.target.name]: value,
});
}
const taskFields = ["name", "address", "address_extended", "postcode", "locality", "region", "website"];
return (
<div>
<form>
{taskFields.map((field) => {
<FormInput
key={field}
fieldName={field}
fieldValue={submissionState[field]}
onChange={handleChange}
originalValue={payload[field]}
/>
})
}
</form>
</div>
);
};
export default TaskForm;
What I would like to do is include logic in the reset button function so that any edits which were made in a form input (from state) get reverted to the original value (stateless), which comes from the payload props: payload[field].
The form input is controlled through a global shared state submissionState, so the reset button logic can either modify the shared state itself with something like:
const handleReset = (submissionState,setSubmissionState) => {
setSubmission({
...submissionState,
fieldName: originalValue,
});
but I would need to pass the submissionState and setSubmission down through to the child component. It would be better if I can somehow update the value attribute in the input, which in-turn should potentially update the shared state? And the logic can just be something like this (assuming I can somehow access the input's value state in the reset button)
const handleReset = (?) => {
/*psuedo code:
setInputValueState(originalValue)
*/
}
I would highly recommend using react-hook-form if it's an option. I've implemented it across several projects and it has never let me down. If it's just not possible to use a library, then keep in mind that React is usually unidirectional. Don't try to work around it, since it works that way by design for most cases you can encounter. Otherwise…
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
const upsertSubmission = (upsert) =>
setSubmission({
...submissionState,
...upsert,
});
const handleChange = ({ target }) => {
upsertSubmission({
[target.name]: target.value,
});
};
const reset =
(originalValue) =>
({ target }) => {
upsertSubmission({
[target.name]: originalValue,
});
};
/* Also something like this. RHF will handle most of this for you!
* const reset = (originalValue, fieldName) =>
* upsertSubmission({[fieldName]: originalValue})
*/
const taskFields = [];
return (
<div>
<form>
{taskFields.map((field) => (
<FormInput
key={field}
fieldName={field}
onChange={handleChange}
reset={reset(originalValue)}
value={submissionState[field]}
/>
))}
</form>
</div>
);
};
I need a solution for following problem ...
When a user registers succesfully, he gets redirected to the login page with email input field filled out and focus on password field. (email is send over paramaters)
Now since onChange event was not triggered (because email was not typed) it's value returns undefined and therefore login fails.
Is there a way to get around this?
You can found my code below
const email = queryString.parse(location.search).email; // returns email correctly
The following is a component so each attribute is set as name={props.name} value={props.value} etc
const handleChange = (name, value) => {
setData((prev) => ({ ...prev, [name]: value }));
};
<InputField name="email" type="email" onChange={handleChange} value={email ? email : ""}
So when everything is typed manually, data gets updated as expected and everything works fine, but when redirected from register to login with the field filled out through email variable, then email in data will be undefined.
Is there a way to trigger onChange when email is placed as a value inside the input field?
Thanks in advance!
The problem is handleChange is not getting the parameters of name and value
You can try something like: (Assuming name is a variable defined)
<InputField name="email" type="email" onChange={(e, {value}) => handleChange(name, value)} value={email ? email : ""}
I make a simple code, but I don't konw what actually you need, you can check on :
Or
import React, { useState } from "react";
const Sample = (props) => {
// const query = window.location.search;
const query = window.location;
const [value, setValue] = useState(query);
const handleValueOnChange = (e) => {
setValue(e.target.value);
};
console.log(query);
return (
<div>
<input onChange={handleValueOnChange} value={value} />
</div>
);
};
export default Sample;
Hope to help you .
I am making a wizard in React js. I am already populating data in form using get api call. Now I want to edit the data in form, that retains it's state over the application flow.
Redux-Form allow you to pass custom props into Field so you can use this as a way to pass retrived value from your api into rendered component.
Base on your example link you can do this:
Modify renderField to accept custom prop value (or any other name you want) and pass it into input value.
const renderField = ({
input,
label,
type,
value,
meta: { touched, error }
}) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} value={input.value ?
input.value : value} />
{touched && error && <span>{error}</span>}
</div>
</div>
);
Define state variable and change handler:
const [email, setEmail] = useState("");
const handleChange = e => {
setEmail(e.target.value);
};
The initial value of your field (in this example 'Email') can be retrived from api as follow:
useEffect(() => {
/* your api call to fetch data */
fetch(....)
.then(res => setEmail(res.data));
}, []);
Then in your field set props and set onChange handler
<Field
name="email"
type="email"
component={renderField}
label="Email"
props={{ value: email }}
onChange={handleChange}
/>
Here is a working example: https://codesandbox.io/s/redux-form-wizard-example-7v3iy?file=/WizardFormSecondPage.js
Redux-form "Field" component provides onChange property. A callback that will be called whenever an onChange event is fired from the underlying input. This callback allows to get "newValue" and "previousValue" for the Field.
React-final-form "Field" component doesn't have this property.
So, how I can get the same functionality?
React-final-form handles this functionality with a tiny external package.
Basically it is an additional component to add inside the form that binds to the element using its name:
<Field name="foo" component="input" type="checkbox" />
<OnChange name="foo">
{(value, previous) => {
// do something
}}
</OnChange>
The current documentation can be found here:
https://github.com/final-form/react-final-form-listeners#onchange
The idea under change detection is to subscribe to value changes of Field and call your custom onChange handler when value actually changes. I prepared simplified example where you can see it in action. Details are in MyField.js file.
As the result you can use it just as with redux-form:
<MyField
component="input"
name="firstName"
onChange={(val, prevVal) => console.log(val, prevVal)}
/>
2022 JANUARY UPDATE
While the code above still works (check the sandbox version) there is a case when the solutions requires more tweeks around it.
Here is an updated sandbox with an implementation via the hooks. It's based on a useFieldValue hook and OnChange component as a consumer of this hook. But the hook itself can be used separately when you need previous value between re-renders. This solution doesn't rely on meta.active of the field.
// useFieldValue.js
import { useEffect, useRef } from "react";
import { useField } from "react-final-form";
const usePrevious = (val) => {
const ref = useRef(val);
useEffect(() => {
ref.current = val;
}, [val]);
return ref.current;
};
const useFieldValue = (name) => {
const {
input: { value }
} = useField(name, { subscription: { value: true } });
const prevValue = usePrevious(value);
return [value, prevValue];
};
export default useFieldValue;
// OnChange.js
import { useEffect } from "react";
import useFieldValue from "./useFieldValue";
export default ({ name, onChange }) => {
const [value, prevValue] = useFieldValue(name);
useEffect(() => {
if (value !== prevValue) {
onChange(value, prevValue);
}
}, [onChange, value, prevValue]);
return null;
};
Another nice option is this answer: https://stackoverflow.com/a/56495998/3647991
I haven't used redux-form, but I added a super simple wrapper around the Field component to listen to onChange like this:
const Input = props => {
const {
name,
validate,
onChange,
...rest
} = props;
return (
<Field name={name} validate={validate}>
{({input, meta}) => {
return (
<input
{...input}
{...rest}
onChange={(e) => {
input.onChange(e); //final-form's onChange
if (onChange) { //props.onChange
onChange(e);
}
}}
/>
)}}
</Field>
);
};
One could use the Field's parse attribute and provide a function that does what you need with the value:
<Field
parse={value => {
// Do what you want with `value`
return value;
}}
// ...
/>
You need to use the ExternalModificationDetector component to listen for changes on the field component like this:
<ExternalModificationDetector name="abc">
{externallyModified => (
<BooleanDecay value={externallyModified} delay={1000}>
{highlight => (
<Field
//field properties here
/>
)}
</BooleanDecay>
)}
</ExternalModificationDetector>
By wrapping a stateful ExternalModificationDetector component in a
Field component, we can listen for changes to a field's value, and by
knowing whether or not the field is active, deduce when a field's
value changes due to external influences.
Via - React-Final-Form Github Docs
Here is a sandbox example provided in the React-Final-Form Docs: https://codesandbox.io/s/3x989zl866
I'm pretty new to React & Redux-Form and at the moment I am in need of some help.
Basically I have listing page with lists and their edit button. When the edit is clicked, I am showing a modal pop up with the fields and doing a API call to fetch the respective list data.
Could you please let me know how can I pre-populate the fields in the modal pop up with the data received from API call?
A demonstration with a code sample will be much appreciated(like I said I am pretty new to React & Redux & Redux-form). :(
Thanks in advance!
Here is the flow:
componentDidMount() {
this.props.loadClient(); // dispatch an action from your component
}
action dispatcher
loadClient() {
// API call here
}
Store the result in redux reducer
case LOAD_PROFILE_SUCCESS:
return {
...state,
data: action.result // save your data here
};
Then add the initialValues props to the #connect decorator
#connect(state => ({
initialValues: state.profile.data // load the saved data here
}),
{ loadClient }
)
Example:
You could load the intialvalues at reduxForm itself.
let AddUser = props => {
const { handleSubmit, initialValues } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="name">Name</label>
<Field name="name" component="input" type="text" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field name="email" component="input" type="email" />
</div>
<div>
<label htmlFor="phoneno">PhoneNo</label>
<Field name="phoneNo" component="input" type="text" />
</div>
<button type="submit">Submit</button>
</form>
);
}
export default AddUser = reduxForm({ form: 'addUser', initialValues: {
name: "abc",
email: "abc#gmail.com",
phoneNo: "1234567890"
} })(AddUser)
Your component must have the Form component. Rest will be taken care by redux-form.
Note:
Structure of your initialValues must same of form data.
Field name and the object property name should be same.
for more detail, you could refer redux-form official page Here
If youre using redux, you just have to map your state in initialValues;
Here's how I do it:
const mapStateToProps = (state) => {
const { state } = state;
return {
initialValues: state,
}
}
then set enableReinitialize to true
FormName = reduxForm({
form: 'formname',
enableReinitialize : true
})(FormName);
then connect your app to your redux store
export default connect(mapStateToProps)(FormName);
Thank you #Sachidhanandhan!
Here's what I did -
Defined the reduxForm with enableReinitialize: true
In the mapStateToProps of connect(), I initialized the data that I received via API and stored in State with initialValues
Connected the reduxForm with connect() => This was the order I missed earlier
It is working like a charm now.
Cheers!
enableReinitialize : boolean [optional]
When set to true, the form will reinitialize every time the initialValues prop changes. Defaults to false. If the keepDirtyOnReinitialize option is also set, the form will retain the value of dirty fields when reinitializing.
Set this to true