Redux Forms - Ignore initialValues with no registered field - reactjs

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.

Related

React: Get state of children component component in parent

I have this container where and is not placed in the same level. How can I get the state of the Form when I click on the button (which is placed on the parent) ?
I've created a demo to address my issue.
https://codesandbox.io/s/kmqw47p8x7
class App extends React.Component {
constructor(props) {
super(props);
}
save = () => {
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form />
<button onClick={this.save}>save</button>
</div>
);
}
}
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
To access a child instance from parent, your need to know about ref:
First, add formRef at top your App class:
formRef = React.createRef();
Then in App render, pass ref prop to your Form tag:
<Form ref={this.formRef} />
Finaly, get state from child form:
save = () => {
alert("how to get state of Form?");
const form = this.formRef.current;
console.log(form.state)
};
Checkout demo here
ideally, your form submit action belongs to the Form component
You can put button inside your From component and pass a submit callback to the form.
class App extends React.Component {
constructor(props) {
super(props);
}
save = (data) => {
// data is passed by Form component
alert("how to get state of Form?");
//fire api call
};
render() {
return (
<div>
<Form onFormSubmit={this.save} />
</div>
);
}
}
you can write the code like this
https://codesandbox.io/s/23o469kyx0
As it was mentioned, a ref can be used to get stateful component instance and access the state, but this breaks encapsulation:
<Form ref={this.formRef}/>
A more preferable way is to refactor Form to handle this case, i.e. accept onChange callback prop that would be triggered on form state changes:
<Form onChange={this.onFormChange}/>
One thing I don't want to do is sync the state for onChange event, because within Form there might be another Form.
Forms will need to handle this any way; it would be impossible to reach nested form with a ref from a grandparent. This could be the case for lifting the state up.
E.g. in parent component:
state = {
formState: {}
};
onFormChange = (formState) => {
this.setState(state => ({
formState: { ...state.formState, ...formState }
}));
}
render() {
return (
<Form state={this.state.formState} onChange={this.onFormChange} />
);
}
In form component:
handleChange = e =>
this.props.onChange({
[e.target.name]: e.target.value
});
render() {
return (
<input
onChange={this.handleChange}
name="firstName"
value={this.props.state.firstName}
/>
);
}
Here is a demo.

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

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!

How to recursively pass up all data from form?

I've got a form, it looks like this:
export default class BookingForm extends React.Component {
constructor(props) {
super(props);
this.state = {data: props.data};
}
render() {
const {booking, vehicleSelect, vehicleData, customer, drivers, fees, occasions} = this.props;
return (
<form className="grid-form">
<div className="row">
<div className="col">
<label>Is this a new or returning customer?</label>
<RadioMenu name="repeat_customer">
<RadioButton value="NEW">New Customer</RadioButton>
<RadioButton value="EXIST">Returning Customer</RadioButton>
</RadioMenu>
</div>
</div>
<div className="row new-customer-row">
<div className="col-1-2">
<label htmlFor="customer.first_name">First Name</label>
<Input id="customer.first_name" name="customer.first_name" type="text"/>
</div>
<div className="col-1-2">
<label htmlFor="customer.last_name">Last Name</label>
<Input id="customer.last_name" name="customer.last_name" type="text"/>
</div>
</div>
// .. more inputs ..
Where <RadioMenu> renders a list of <RadioButton>s which in turn contain an <Input>.
<Input> just looks like this:
export default function Input(attrs) {
return <input {...attrs}/>;
}
I made it a React component hoping I can do something useful with it.
Basically, I want all the form data to be stuffed into this.data.INPUT_NAME as soon as the input is changed. If the input name contains a . then I want to put it into a sub-object. For example, customer.last_name will be stored in this.state.data.customer.last_name. I also want to to use this.state.data to set the initial value for all the Input elements without having to explicitly add a value attribute to each of them; it should just know what value to pull out of the data object by using the input's name.
I don't know how to approach this. My first thought is that instead of returning the <form> I should put it into a variable, and then pre-process it, adding onChange and value attributes to anything of type Input, but even I try that, I don't think it would work on my RadioMenu because RadioMenu is not of type Input and I don't think I could recurse down into its children.
I could try using this context feature but the warnings are scaring me away.
I haven't looked into Flux/Reflux/Redux/xyz yet, but I don't think I really want to incorporate another framework this early in the game; I want to understand how to approach this properly before tucking it away.
So, how can I get all my form data into this.state.data?
The radio widgets look like this. I'm open to changing them if necessary. This is my first custom input widget.
// RadioMenu.jsx
import React from 'react';
import {cloneWithProps} from '../helpers/react-helpers';
import Input from './Input';
export default class RadioMenu extends Input {
constructor(props) {
super(props);
this.state = {value: props.value};
}
onChange = ev => {
this.setState({value: ev.target.value});
if(this.props.onChange) {
this.props.onChange(ev);
}
};
render() {
let {children, name, onChange, ...attrs} = this.props;
return (
<div className="radio-horizontal radio-menu" {...attrs}>
{cloneWithProps(children, btn => ({
name,
checked: btn.props.value == this.state.value,
onChange: this.onChange
}))}
</div>
);
}
}
// RadioButton.jsx
export default function RadioButton({children, ...attrs}) {
return (
<label className="checkable">
<input type="radio" {...attrs}/>
<span>{children}</span>
</label>
);
}
I was trying to use inheritance has so I could pluck out all the Inputs, regardless if they're custom or not, but I can't seem to get this to work in React. mycomp.type instanceof Input doesn't return true for sub-classes. I know React suggests composition over inheritance, but I don't know how to make that work.
This kind of problem is the reason we have libraries/patterns like Redux/Flux, but that doesn't mean it's not possible to solve without React, just a little bit harder.
In this specific case, you have a few options.
Child-Parent Events
If you change your <RadioButton /> component to accept an onChange handler, then you can listen for changes to the button and put them straight into your state.
function RadioButton(props) {
return (
// pass the onChange prop down
<input type="radio" onChange={props.onChange} />
);
}
Then update your <BookingForm /> component to make use of this new handler prop.
const setRadioState = e => this.setState({ radio: e.target.value });
// ...
<RadioMenu name="repeat_customer">
<RadioButton value="NEW" onChange={setRadioState}>New Customer</RadioButton>
<RadioButton value="EXIST" onChange={setRadioState}>Returning Customer</RadioButton>
</RadioMenu>
Accessing the Form
You can listen to the form for the submit event then iterate through the form's elements to build up an object you can put in your state.
render() {
// ...
<form onSubmit={this.onSubmit}>
// ...
},
onSubmit(e) {
const form = e.target;
const elements = form.elements;
// remove numeric keys
const keys = Object.keys(elements).filter(k => /[^\d]/.test(k);
const data = {};
keys.forEach(k => data[k] = elements[k].value);
this.setState(data);
}
If you aren't listening to the submit event and want to submit with say, a button press, then you'll need to use refs to get a instance of the form.
I'm more or less just making this approach up off the top of my head, so be wary of edge cases.
ReactLink will do what you want, but it's on its way out the door. Fortunately, it's easy to recreate this functionality in just a few lines of code.
Instead of using <input>, you can use this component:
import React from 'react';
export default class LinkedStateInput extends React.Component {
render() {
const {value, ...attrs} = this.props;
return <input {...attrs} value={value.value} onChange={ev => value.requestChange(ev.target.value)} />;
}
}
Usage example:
<LinkedStateInput value={this.linkState('passenger_count')} type="text"/>
Now just add a method to your BookingForm to handle the state updates:
linkState(name) {
return {
value: _.get(this.state.data, name, ''),
requestChange: value => {
let data = _.clone(this.state.data);
_.set(data, name, value);
this.setState({data})
}
}
}
I've used lodash here to handle deep sets/gets.
RadioMenu becomes even simpler because now it doesn't even have to remember its own state:
export default function RadioMenu({children, name, valueLink}) {
return (
<div className="radio-horizontal radio-menu">
{
valueLink
? cloneWithProps(children, btn => ({
name,
checked: btn.props.value === valueLink.value,
onChange: ev => valueLink.requestChange(ev.target.value)
}))
: cloneWithProps(children, {name})
}
</div>
);
}

Resources