Redux Form Field Array Validation - reactjs

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;
};

Related

How to get children values on antd's form submit?

I made a custom component called TagsInput, and added it to a form. My problem is, when the form is submitted, the value for field tags is always undefined, because the Form.Item cannot retrieve the value from the TagsInput child. This is usually not a problem when children of Form.Item are antd components.
export default function NewProjectForm(props) {
return (
<Form layout="vertical" onFinish={props.onSubmit} id="newProjectForm">
<Form.Item label="Tags" name="tags">
<TagsInput />
</Form.Item>
</Form>
);
}
TagsInput is a class component that has its value within its state:
class TagsInput extends React.Component{
constructor(props){
super(props);
this.state = {
value: []
}
}
render(){
return(<div></div>)
}
}
I dont want to elevate the value field from TagsInput state to NewProjectForm because it is not a class component... I would be doing it just for the sake of solving this issue. I am asking this question because I think there is a cleaner way of solving this.
How can I make NewProjectForm retrieve the value of TagsInput from within its state?
Is there any function I can pass to TagsInput as a property, so that the form asks the component for a value when it is submitted?
In order to achive what you want, you need to handle the state of the form (and its components) at the parent component (NewProjectForm).
If you are willing to keep that component a function component, you can use React Hooks and define a state with useState like this:
const [tagValue, setTagValue] = useState([]);
While tagValue is the value you will get from the child TagsInput, and the setTagValue is the function that can set that value. You need to pass setTagValue as a function prop to TagsInput and use it on change. It will look something like this:
export default function NewProjectForm(props) {
const [tagValue, setTagValue] = useState([]);
return (
<Form layout="vertical" onFinish={props.onSubmit} id="newProjectForm">
<Form.Item label="Tags" name="tags">
<TagsInput setTagValue={(val)=>setTagValue(val)} />
</Form.Item>
</Form>
);
}
class TagsInput extends React.Component{
constructor(props){
super(props);
this.state = {
value: []
}
}
.........
.........
onChangeInput(val){
this.props.setTagValue(val);
}
.........
.........
render(){
return(<div></div>)
}
}
EDIT
After the clarification in the comments, you actually need to follow the steps of using Customized Form Controls, As described here.
You need to pass an object with value and onChange that changes the value. Then, the Form component should manage to get that value automatically.
So I guess it should look something like that:
const TagsInput= ({ value = {}, onChange }) => {
const triggerChange = newValue => {
if (onChange) {
onChange(newValue);
}
};
return(<div></div>)
};

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.

reduxForm - initialValues did not apply after update

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)
})

redux form update state and retrieve

I have 2 redux form components, first one is LoginFormComponent which is a redux-form with form name 'submitValidation'
LoginFormComponent = reduxForm({
form: 'submitValidation'
})(LoginFormComponent)
This form component has input field like so:-
<Field name="userid" size="22" placeholder="Personal ID"
maxLength="22" component="input" type="text"/>
On form submit, I want to navigate to the second redux form component which is VerifyLoginBodyComponent and would like to show the submitted "userid" from the first component inside a div like so:-
<div className="tieringElement">
You are logging in as: <b>{userid}</b>
</div>
This is how I handle submit in the first form component
class LoginFormComponent extends Component {
constructor(props){
super(props);
this.submit=this.submit.bind(this);
}
submit(values) {
console.log(JSON.stringify(values));
this.context.router.push('/verify');
}
render(){
const { handleSubmit } = this.props
return(
<form onSubmit={handleSubmit(this.submit)} id="loginform">
<Field name="userid" size="22" placeholder="Personal ID" maxLength="22" component="input" type="text"/>
</form>
)
}
LoginFormComponent = reduxForm({
form: 'submitValidation'
})(LoginFormComponent)
export default LoginFormComponent;
This is my redux store:
const INITIAL_APP_STATE = {
form: {
submitValidation: {
userid: ''
}
}
};
How can I access the submitted userid from LoginFormComponent inside VerifyLoginBodyComponent?
I checked https://redux-form.com/6.5.0/examples/selectingFormValues/, Redux-form handleSubmit: How to access store state? and redux-form : How to display form values on another component, but still unable to display the userid in second form component.
Please provide any suggestions.
redux-form provides formValueSelector that you can use to create a selector that retrieves form values from the redux store. You can sue that in the mapStateToProps -function of your VerifyLoginBodyComponent.
Example:
const selector = formValueSelector('submitValidation')
const mapStateToProps = reducers => {
const signingInUserId = selector(reducers, 'userid')
return { signingInUserId }
}
This depends of course on the fact that the form values are not cleared from the store until after submitting succeeds.
Hope this helps!

Resources