reduxForm - initialValues did not apply after update - reactjs

I use Redux Form Version 6.8.0.
I have a form component which get's it's "initialValues" via a "mapStateToProps" function. On the first render everything works quite fine.
Following my form config:
const CategoryChangeForm = reduxForm({
form: 'change-category',
validate: newCategoryValidate,
enableReinitialize: true
})(CategoryChange);
When i change the Field "category" and the update succeed, i receive the new updated value from firebase.
I pass this updated value via the mentioned "mapStateToProps" function to the "initialValues":
function mapStateToProps(state, ownProps) {
return {
initialValues: { category: state.categories[ownProps.id].name, id: ownProps.id }
};
}
I expected that the new Value would be applied to the "category"-Field Component. But it doesn't get the updated value.
The config of my "Field" Components:
const fieldProps = {
category: {
name: 'category',
type: 'text',
placeholder: 'Bezeichnung',
component: InputField
},
hidden: {
name: 'id',
type: 'hidden',
component: 'input'
}
};
and here is my Form Component:
export const CategoryChange = props => {
const {
color,
message,
handleSubmit,
stateComponent
} = props;
console.log("Props: ", props);
return (
<span>
<Subline color={ color } marginTop={ 70 } marginBottom={ 30 } align="center">{ message }</Subline>
<form>
<Field { ...fieldProps.category } />
<Field { ...fieldProps.hidden } />
<Button onClick={ handleSubmit(changeCategory.bind(stateComponent)) }>Ändern</Button>
<Button marginBottom={ 5 } marginTop={ 10 }>Löschen</Button>
</form>
</span>
);
}
I can observe that after an update, my form component rerenders 2 times. First time its prop "initialized" is set to "true". But the second time its set to "false".
The second render occurs due to a stateChange of the hoc component which wrapped my form component. The "setState" for the hoc is triggered when the update was successful and show an appropriate message to the user.
But cause of the second render the form component did not initialize.
If you need any more code to see, let me know.
Hope someone has a hint to solve this problem...

According to the docs:
By default, you may only initialize a form component once via
initialValues. There are two methods to reinitialize the form
component with new "pristine" values:
Pass a enableReinitialize prop or reduxForm() config parameter set to
true to allow the form the reinitialize with new "pristine" values
every time the initialValues prop changes. To keep dirty form values
when it reinitializes, you can set keepDirtyOnReinitialize to true. By
default, reinitializing the form replaces all dirty values with
"pristine" values.
Dispatch the INITIALIZE action (using the action creator provided by
redux-form).
You can change CategoryChangeForm from function to class and call initialize action from redux-forms in the componentWillReceiveProps method.
import {initialize} from 'redux-form'
CategoryChangeForm extends Component {
...
componentWillReceiveProps(nextProps) {
// check the props and call initialize when needed
}
}
mapDispatchToProps = dispatch => ({
initialize: (data) => initialize('change-category', data)
})

Related

Using react-hook-form in React with Typescript in child component

I am using a basic example of the react-hook-form library and even after looking up the documentary, I do not know how to pass the data from the form to another component. Here is my form component:
import { useForm, SubmitHandler } from "react-hook-form";
type FormInputs = {
minimalFrequency: number;
maximialFrequency: number;
};
// const onSubmit: SubmitHandler<FormInputs> = data => console.log(data);
export default function BasicUsage() {
const { register, formState: { errors }, handleSubmit, getValues } = useForm<FormInputs>({
defaultValues: {
min: 250,
max: 8000,
}
});
const onSubmit = (data: any) => {
console.log(data);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("minimalFrequency", { required: true })} />
{errors.minimalFrequency && "Only numbers are allowed"}
<input {...register("maximialFrequency", { required: true })} />
{errors.maximialFrequency && "Only numbers are allowed"}
<input type="submit" />
</form>
);
}
I would want to get the min and max values, in form of the given data object, of the user after they pushed the "submit" button and I just can't get my head around how it works.
My main component is a quite large class component, and I read that it might not work because react-hook-form needs a functional component. If true, is there a way to still use my class component somehow?
UPDATE: Added the parent component
import { useState } from "react";
import React from "react";
import BasicUsage from "./BasicUsage"
type Props = {
}
type State = {
dataFreq: object;
}
export default class Parent extends React.Component<Props, State>{
private timer: any;
constructor(props: Props) {
super(props);
this.state = {
dataFreq: {
minimalFrequency: 250,
maximialFrequency: 8000
}
};
}
getDataFromForm = (dataFreq: any) => {
this.setState({dataFreq: dataFreq })
console.log(dataFreq)
};
render() {
const minFreq = this.state.dataFreq;
console.log("This is a this dataFreq", this.state.dataFreq);
console.log("This is a this minimalFrequency", minFreq);
return (
<div>
<BasicUsage getDataFromForm={this.getDataFromForm}/>
</div>
);
}
}
You are still able to use your class component as a parent.
If I am I correct in assuming that you want to use data from the form in your main component, and the main component is the parent, you can define a function in your main component, something like
getDataFromForm(data){
this.setState({data: data })
}
Then you pass this function into your BasicUsage component
//In your main components render function, or wherever you are using the BasicUsage component
<BasicUsage
//other props you want to send into BasicUsage from the main component
getDataFromForm={getDataFromForm}
/>
Now in your BasicUsage component's onSubmit function you can call the function you passed as a prop as such
const onSubmit = (data: any) => {
//Do something with your data if you want to change format or process it somehow;
//in this case you should probably make a new variable and pass the new variable into getDataFromForm
props.getDataFromForm(data) //Call the function in the parent component
}
If you're using the form data in a sibling component and not a parent component, you would make the getDataFromForm function in a common parent and pass the function to the BasicUsage component and the state.data value into the sibling component where you want to access the data

How to conditionally render component in <Field> of redux-form

In my code, was trying to render a <DatePicker> in <Field> component of Redux Form, via a function called renderDatePicker(). This function is linked to handleClick() function where the state variable isOpen is set to true.
So ideally, onClick should render the <DatePicker> as it is set to visible. But code doesn't update anything. Where am I doing wrong here?
Note: Rendering <DatePicker> alone directly without the help of <Field component=...>, works fine.
For debugging, complete code is in CodeSandbox,
SimpleForm.js
import React from "react";
import { reduxForm } from "redux-form";
import { Field } from "redux-form";
import DatePicker from "react-mobile-datepicker";
class SimpleForm extends React.Component {
constructor(props) {
super(props);
this.state = {
time: new Date(),
isOpen: false
};
this.handleClick = this.handleClick.bind(this);
}
renderDatePicker = () => (
<DatePicker
value={this.state.time}
isOpen={this.state.isOpen}
onSelect={this.handleSelect}
onCancel={this.handleCancel}
/>
);
handleClick() {
this.setState({ isOpen: true }, function() {
console.log(this.state.isOpen);
});
}
handleCancel = () => {
this.setState({ isOpen: false });
};
handleSelect = time => {
this.setState({ time, isOpen: false });
};
render() {
return (
<div>
<button className="select-btn" onClick={this.handleClick}>
select time
</button>
<Field
name="date"
type="date"
component={this.renderDatePicker}
label={"date"}
/>
</div>
);
}
}
export default reduxForm({
form: "simple" // a unique identifier for this form
})(SimpleForm);
The reason for this behavior lies in the implementation of react-form's Field component. It does a shallow compare of all its properties to decide whether it should rerender. You can change your component's state as much as you like, the reference to this.renderDatePicker won't change.
Field passes properties including an onChange handler and the current value into the field's component stateless function call to notify of changes, but this doesn't really apply here because your toggle button is outside of the field.
So one option that comes to my mind is to move your button into the rendered field and then call onChange(!value).
The easier yet dirtier option would be to use an arrow function in your component property: component={() => this.renderDatePicker()} - this instance changes with every re-render of your SimpleForm (i.e. if the state changes), so it comes with a cost, but depending on the complexity of your application the cost is negligible. To mitigate the impact, you could implement shouldComponentUpdate (just like redux-form's Field does) to decide whether it should rerender or not, based on the current and next isOpen state.
Check this bit in redux-form for more details: https://github.com/erikras/redux-form/blob/master/src/createField.js#L44

Redux Forms - Ignore initialValues with no registered field

I have an object fetched from the server that contains lots of fields not relevant to the current form. I'd like to pass the whole object to initialValues on my form, but when I submit, I don't want the extra fields to carry through.
Here's a simple form:
const MyForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="name" component="input" placeholder="Name" />
<button type="submit">Submit</button>
</form>
);
export default reduxForm({
form: "foo",
onSubmit: values => {
console.log(values);
},
})(MyForm);
And in its parent component, it's rendered like so:
<MyForm initialValues={{ name: "bob", other: "thing" }} />
When I submit the form, I want values to look like:
{name: "bob"}
and not include the extra other field. Is this possible?
Since the extra fields are not visible to the end user, they shouldn't be part of the form's eventual patch request. I also don't want my parent component to have be coupled tightly to the form, so I don't want to filter the fields at that level.
We ended up solving this by wrapping the form with a higher-order component. You can then wrap the passed-in submission handler and perform some logic on the values returned by the form before they get sent to the submission handler:
export function specialForm(WrappedForm) {
return class extends Component {
submissionFilter(submitFunction) {
return function(values, dispatch, props) {
// Only let registered fields through.
if (props.registeredFields) {
values = _.pick(values, Object.keys(props.registeredFields));
}
return submitHandler(values, dispatch, props)
}
}
render() {
var submitHandler = this.props.onSubmit;
if (submitHandler) {
submitHandler = this.submissionFilter(submitHandler);
}
return <WrappedForm {...this.props} onSubmit={submitHandler} />;
}
};
}
The key to making this work is the fact that the form's props are passed as the 3rd argument to the submission handler, and the props have an attribute called registeredFields which are the fields in the form itself.

react-select does not clear value when redux-form is reset

I have a stateless React function to render a react-select Select to a form. Everything else works nicely with redux-form except when redux-form is reset. I need to reset the form manually after successful post.
onChange and onBlur change the redux-form value correctly when Select has a value change. When I reset the redux-form, the redux-form value is cleared but the Select will have the old value.
function SelectInput(props) {
const { input, options, label } = props;
const { onChange, onBlur } = input;
const handleChange = ({ value }) => {
onChange(value);
};
const handleBlur = ({ value }) => {
onBlur(value);
};
return (
<FormField {...props}>
<Select placeholder={label} options={options} onChange={handleChange} onBlur={handleBlur} />
</FormField>
);
}
I converted the SelectInput to React.PureComponent, and added the value as a state inside the component and looked for when the Component received new props:
constructor(props) {
super(props);
this.state = {value: ''}
}
componentWillReceiveProps(nextProps){
this.setState({value: nextprops.input.value})
}
<Select value={this.state.value} placeholder={label} options={options} onChange={handleChange} onBlur={handleBlur} />
With this Select was not able to show the value at all.
The problem is that how I can update the Select to show empty value when redux-form that this field is part of is reset? Redux-form resets the value corretly inside the redux state and if I try to submit the form, validation notices that that Select has empty value. The Select will however display the old value so that user thinks that there is a value selected.
Reset is done by dispatching reset in the actual redux-form component. Redux devtools show that fields are reset and the redux state is cleared from all the value, Select component just won't update the DISPLAYED value to empty.
const afterSubmit = (result, dispatch) =>
dispatch(reset('datainputform'));
export default reduxForm({
form: 'datainputform',
onSubmitSuccess: afterSubmit,
})(DataInputForm);
Versions I use:
react-select#v2.0.0-beta.6
redux-form#7.3.0
You can also set a key at the form level itself. The key will take a unique value that you can store in the component state. This unique value will be updated every time reset is hit.
state = {
unique_key: ''
}
// this is the onClick handler for reset button on the form
onResetForm = () => {
reset_val = Date.now();
this.props.reset();
this.setState({unique_key: reset_val});
}
<Form actions={action_func}, key={this.state.unique_key}/>
Now whenever reset is clicked, the handler will update the unique_key. This will result in re-rendering the Form with the default values. The handler also calls the form reset function to clear the redux.
Got it working. Problem was handling the Select null value. Changed stateless function to PureComponent, added the value to state.
constructor(props) {
super(props);
this.state = { value: '' };
}
Redux-form changes the react-select value by sending new props. So added
componentWillReceiveProps(nextProps) {
if (nextProps.input.value === '') {
this.setState({ value: '' });
}
}
Added setState to handleChange:
handleChange = (data) => {
const value = data === null ? '' : data;
this.setState({ value });
this.props.input.onChange(data.value);
};
And then added the value prop.
<Select value={this.state.value}...

Redux Form Field Array Validation

I am using the Redux Form module for all forms in the project. I want to create a validation based on the component props. There is a table with some fields on every row.
The table
The first field is a dropdown with all the products which come from the store. Every product has an available quantity and if the field quantity is more than the available quantity the Redux Form should return an error for the specific row. I can't do it in the validate function that is passed in the reduxForm method:
reduxForm({
validate,
form: 'theNameOfTheForm'
})
The reason why I can't do the table validation in the validate function is because it can't see the current props of the component (I didn't find a way how I can do that). I've decided to pass the validate function as a prop to the FieldArray component:
// some methods
validate(values) {
// some validation code
}
render() {
return (
<FieldArray validate={this.validate} />
)
}
From the validate method I can access the component props but whatever I return from this method an error is not received by the field prop in the component passed as a component prop to the <FieldArray />.
return 'Some error message';
return ['Some error message'];
return {
products: 'Some error message'
};
return {
products: ['Some error message']
};
<FieldArray validate={this.validate} component={FieldArrayComponent} />
const FieldArrayComponent = ({ field, meta }) => {};
How I can do the validation? Am I doing something wrong? Is there another way how I can do the validation?
You can pass the component props to the validate function when using HOC (higher order components) and do the validation in the main validate function (no need to create a method in the component and then pass it to the FieldArray component). Just export the component like this:
export default connect(
mapStateToProps,
mapDispatchToProps
)(
reduxForm({
validate,
form: 'ExampleForm'
})(ExampleForm)
);
The component props are passed in the validation function as a second parameter:
const validate = (values, props) => {
const errors = {};
return errors;
};

Resources