react final form add extra fields as props - reactjs

Hello I have two forms that are different by only one field. So I am thinking of create a common form component and pass only the extra field as an optional property.
Common form:
import { Form as ReactFinalForm, Field } from "react-final-form";
import Form from 'react-bootstrap/Form';
interface CommonFormProps {
onSubmit: (values: any) => void;
extraFields?: typeof Field[];
}
export default function CommonForm(props: CommonFormProps) {
return (
<ReactFinalForm
onSubmit={values => {
props.onSubmit(values);
}}
render={({ handleSubmit, form, submitting, pristine, values}) => (
<Form onSubmit={handleSubmit}>
// other fields
{props.extraFields}
</Form>
)}
/>
);
}
Form that needs extra field
export default function FormB() {
return (
<CommonForm
extraFields={<Field name="price" validate={required} >
{({ input, meta }) => (
<Form.Group as={Form.Row} controlId="price">
<Form.Label column sm={4}>
Price
</Form.Label>
<Col sm={8}>
<Form.Control type="text" {...input} />
</Col>
</Form.Group>
)}
</Field>}
/>
);
}
I have tried to define extraFields as typeof Field[] or typeof Field. It makes no difference and error is always
Type 'Element' is not assignable to type '<FieldValue = any, RP
extends FieldRenderProps<FieldValue, T> = FieldRenderProps<FieldValue,
HTMLElement>, T extends HTMLElement = HTMLElement>(props:
FieldProps<...>) => ReactElement<...>'. Type 'Element' provides no
match for the signature '<FieldValue = any, RP extends
FieldRenderProps<FieldValue, T> = FieldRenderProps<FieldValue,
HTMLElement>, T extends HTMLElement = HTMLElement>(props:
FieldProps<...>): ReactElement<...>'.ts(2322) index.tsx(16, 5): The
expected type comes fro
Could anybody help get rid of this please?

Pass as a child, not a prop:
export default function FormB() {
return (
<CommonForm>
<Field name="price" validate={required} >
{({ input, meta }) => (
<Form.Group as={Form.Row} controlId="price">
<Form.Label column sm={4}>
Price
</Form.Label>
<Col sm={8}>
<Form.Control type="text" {...input} />
</Col>
</Form.Group>
)}
</Field>}
</CommonForm>);
}
Then use children
export default function CommonForm(props: CommonFormProps) {
return (
<ReactFinalForm
onSubmit={values => {
props.onSubmit(values);
}}
render={({ handleSubmit, form, submitting, pristine, values}) => (
<Form onSubmit={handleSubmit}>
// other fields
{props.children}
</Form>
)}
/>
);
}
children can be empty

Related

What are the types of react-hook-form methods?

I am using react-hook-form and I want to have certain parts of the form in an extra component. Therfore I need to pass down some methods. What are the correct typescript types of the methods: register, control, setValue, watch ?
Example Sandbox
interface INameInput {
register: any;
setValue: any;
watch: any;
control: any;
}
const NameInput: React.FC<INameInput> = (props: INameInput) => {
const { register, control, setValue, watch } = props;
return (
<>
<label>First Name</label>
<input name="firstName" ref={register} />
<label>Last Name</label>
<input name="lastName" ref={register} />
</>
);
};
export default function App() {
const { register, control, setValue, watch} = useForm<FormValues>();
return (
<form >
<NameInput
register={register}
control={control}
setValue={setValue}
watch={watch}
/>
</form>
);
}
Here is a page which contains the list of exported Types
https://react-hook-form.com/ts
I think you are after the following type
https://react-hook-form.com/ts#UseFormMethods
eg:
UseFormMethods['register']

Use combineValidators for SelectInput to validate Guid.Empty

I am looking for a way to validate Select Input before submit final form in React App, I tried below but not working :
When I print the values in handle submit for each is : 00000000-0000-0000-0000-000000000000
How can add the validation error when the Guid is empty
Added in the Final form file react .tsx :
const validate = combineValidators({
projectId: isRequired("The project"),
achievementStageId: isRequired("The project item achievement stage"),
projectItemStatusId: isRequired("The project item status"),
});
<Form onSubmit={handleSubmit} loading={loading}>
<Field
name="projectId"
placeholder="Select Project"
component={SelectInput}
options={projectsDdl}
multiple={true}
/>
<Field
name="achievementStageId"
placeholder="Select Achievement Stage"
component={SelectInput}
options={achievementStagesDdl}
multiple={true}
/>
<Field
name="projectItemStatusId"
placeholder="Select Project Item Status"
component={SelectInput}
options={projectItemStatusDdl}
multiple={true}
/>
projectsDdl, achievementStagesDdl, projectItemStatusDdl as array from APIs
And the component={SelectInput}
import React from "react";
import { FieldRenderProps } from "react-final-form";
import { FormFieldProps, Form, Label, Select } from "semantic-ui-react";
interface IProps extends FieldRenderProps<string, any>, FormFieldProps {}
const SelectInput: React.FC<IProps> = ({
input,
width,
options,
placeholder,
meta: { touched, error },
}) => {
return (
<Form.Field error={touched && !!error} width={width}>
<Select
value={input.value}
onChange={(e, data) => input.onChange(data.value)}
placeholder={placeholder}
options={options || []}
/>
{touched && error && (
<Label basic color="red">
{error}
</Label>
)}
</Form.Field>
);
};
export default SelectInput;

How to add react-phone-number-input to -react-final-form?

I'm currently creating a form using react-final-form and trying to use react-phone-number-input with it through integration as an adapter, as displayed through this example.
I attempted to use the example to learn how it is done, but I'm not sure how to access the component and create the adapter for it properly.
import React from 'react';
import { Form, Field } from 'react-final-form';
import PhoneInput from 'react-phone-number-input';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const onSubmit = async values => {
await sleep(300)
window.alert(JSON.stringify(values, 0, 2))
}
const PhoneAdapter = ({ input, meta, ...rest }) => (
<PhoneInput
{...input}
{...rest}
value={input.value}
onChange={(event, value) => input.onChange(value)}
/>
)
class ContactForm extends React.Component {
render() {
return (
<>
<Form
onSubmit={onSubmit}
initialValues={{ }}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}>
<fieldset>
<Field component={PhoneAdapter} />
</fieldset>
<fieldset>
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
</fieldset>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
</>
);
}
}
export default ContactForm;
Update: July 2019
Apparently, all you need to do is to spread the input property of Field. Works flawlessly. Learn about spreading if you're not familiar with it.
const PhoneAdapter = ({ input }) => (
<PhoneInput {...input} />
)
<Field name="phone" placeholder="Enter phone number" component={PhoneAdapter} />
I ended up experimenting with the FieldRenderProps props until it worked out. I wasn't so sure whether it would work or not, as react-phone-number-input is two elements in a component. I thought it would implement the input on only one of the elements.
By using input, I gain access to the input's props. Hence, I called upon it's value, as the default looks like so:
<PhoneInput
placeholder="Enter phone number"
value={ this.state.value } // This is what I called.
onChange={ value => this.setState({ value }) }/>
I then did the same for the onChange function prop.
const PhoneAdapter = ({ input }) => (
<PhoneInput value={input.value.value} onChange={value => input.onChange(value)} />
)
Finally, I used the component adapter like so:
<Field name="phone" placeholder="Enter phone number" component={PhoneAdapter} />

Control the Redux-form with the button

I am using the Redux-form to do a task.
This form in a form container.
In the form container or in the form component.
There are two buttons. An add button and a subtract button.
The form component is:
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
const renderTextField = ({ input, label, meta: { touched, error }, ...custom }) => (
<TextField hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
)
const ActivityDetailForm = props => {
const { handleSubmit, pristine, reset, submitting,} = props
return (
<form onSubmit={handleSubmit}>
<div>
<RaisedButton
type="submit"
disabled={pristine || submitting}
label="saveChange"
fullWidth={true}
secondary={true}
/>
</div>
</form>
)
}
export default reduxForm({
form: 'ActivityDetailForm', // a unique identifier for this form
})(ActivityDetailForm)
Now, I face a problem. When I click the add button,
<div>
<Field name="field1" component={renderTextField} label="text1: "/>
</div>
the code above will be created in the form element.
When I click the add button again, the div element which includes the Field named field2 will be created in the form element.
... Field named field3
... Field named field4
... Field named field5
...
When I click the subtract button. The last Field element will be destroyed.
Do you know the method to solve this problem?
The following (untested) is a pretty basic example on how to achieve dynamic inputs with a FieldArray. You'd have to tweak this a bit to tailor it to your specific scenario.
const renderTextField = ({ input, label, meta: { touched, error }, ...custom }) => (
<TextField hintText={label}
floatingLabelText={label}
errorText={touched && error}
{...input}
{...custom}
/>
)
const ActivityDetailForm = props => {
const { handleSubmit, pristine, reset, submitting,} = props
const renderFieldArray = ({ fields }) => (
<div>
<div>
<RaisedButton
onTouchTap={() => fields.push({})}
label="Add"
/>
</div>
{fields.map((field, index) => (
<div>
<div key={index}>
<Field
name={`${field}.name`}
label={`Text ${index + 1}`}
component={renderTextField}
/>
</div>
<div>
<RaisedButton
onTouchTap={() => fields.remove(index)}
label="Remove"
/>
</div>
</div>
))}
</div>
);
return (
<form onSubmit={handleSubmit}>
<div>
<FieldArray
name="textFields"
component={renderFieldArray}
/>
<RaisedButton
type="submit"
disabled={pristine || submitting}
label="saveChange"
fullWidth={true}
secondary={true}
/>
</div>
</form>
)
}
export default reduxForm({
form: 'ActivityDetailForm', // a unique identifier for this form
})(ActivityDetailForm)

render textarea control with react-bootstrap and react-redux-form

I'm currently rendering input controls like so:
renderField = function({ input, label, name, type, meta: { touched, error, warning } }) {
return (
<FormGroup>
<FormControl {...input} type={type} placeholder={label} />
{touched && error && <span className="error-text">{error}</span>}
</FormGroup>
);
}
render() {
const { handleSubmit, pristine, error } = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit)} className="profile-form">
<Field name="first_name" component={this.renderField} type="text" label="First Name" />
<Field name="last_name" component={this.renderField} type="text" label="Last Name" />
<Field name="bio" component={this.renderField} type="text" label="Bio" />
...
<Button bsStyle="primary" type="submit" disabled={pristine}>Save Changes</Button>
</form>
);
}
I want to change the bio field to be a textarea control instead of an input. Is it possible to do this within my renderField function. I'd like to do it there instead of having to replicate for another function such as renderTextArea since that would duplicate a lot of the arguments and bootstrap markup.
I'm not seeing any examples of this in the docs or searches but maybe I'm thinking about it wrong.
thanks to #elmeister for the comment to lead in the right direction. I was missing the componentClass prop, so on the bio field I just needed to change to
<Field name="bio" component={this.renderField} type="text" label="Bio" componentClass="textarea" />
and then in my renderField method I needed to add componentClass as an argument and add that prop to the FormControl component. I didn't need to change input to field btw, i think componentClass just overrides it when passed in.
renderField = ({ input, label, name, type, componentClass, meta: { touched, error, warning } }) => {
return (
<FormGroup>
<ControlLabel>{label}</ControlLabel>
<FormControl {...input} componentClass={componentClass} type={type} placeholder={label} />
{touched && error && <span className="error-text">{error}</span>}
</FormGroup>
);
}
You could also use Control with FormControl straightaway.
export const InputFieldComponent = ({
id,
type,
label,
fieldObject,
placeHolder,
maxLength,
srOnly,
messages, validators, onChange}: Props) => (
<FormGroup controlId={id} validationState={fieldObject.valid ? 'success' : 'error'>
<ControlLabel srOnly={srOnly}>{label}</ControlLabel>
<Control
model={`.${id}`}
type={type}
placeHolder={ placeHolder || label }
component={FormControl}
maxLength={maxLength}
validators={validators}
onChange={onChange}
>
<FormControl.Feedback> <FaCheck /> </FormControl.Feedback>
<Errors
show="touched"
model={`.${id}`}
wrapper={ (v) => <HelpBlock>{v}</HelpBlock> }
messages={messages}
/>
</FormGroup>
);
InputFieldComponent.defaultProps = {
messages: {},
srOnly: false,
type: 'text',
maxLength: 255
}
export default InputFieldComponent;

Resources