React-Final-Form with DropDown in Child Component, how? - reactjs

I'm trying to use React-final-form with a DropDown in a Child Component.
Can't get this to work.
All of my text fields are already in a Child Component and this works like a charm.
The field in the parent looks like this:
<Field
name="lastName"
placeholder="Last Name"
validate={required}
>
{({input, meta, placeholder}) => (
<MyField meta={meta} input={input} placeholder={placeholder}/>
)}
</Field>
The Child Component looks like this:
export const MyField = (props) => {
return (
<Form.Field className={props.meta.active ? 'active' : ''}>
<Label>{props.label ? props.label : props.placeholder}</Label>
<Form.Input
{...props.input}
placeholder={props.placeholder}
className={(props.meta.error && props.meta.touched ? 'error' : '')}
/>
</Form.Field>
)
};
The "Form.Field" and "Label" are coming from semantic-ui-react
But now I want to do the same with a DropDown.
The standard DropDown, taken from an example on the React-Final-Form site, looks like this:
<Field name="toppingsA" component="select">
<option value="chicken">Chicken</option>
<option value="ham">Ham</option>
<option value="mushrooms">Mushrooms</option>
<option value="cheese">Cheese</option>
<option value="tuna">Tuna</option>
<option value="pineapple">Pineapple</option>
</Field>
And it works in a sense that I'm getting the value in my react-final-form values onSubmit.
then I'm trying to offload the Dropdown itself to the Child Component (with the intention to use the semantic-ui-react version of a Dropdown, but first things first and get the Dropdown to work :-) )
Parent Component:
const eatOptions = [
{key: 'c', text: 'Chicken', value: 'chicken'},
{key: 'h', text: 'Ham', value: 'ham'},
{key: 'm', text: 'Mushrooms', value: 'mushrooms'},
{key: 't', text: 'Tuna', value: 'tuna'}
];
// And in the Form:
<Field name="toppingsB" component="select" options={eatOptions}>
{ ({input, meta, options}) => {
return (
<Opts options={options} name={input.name}/>
)
}}
</Field>
And in the Child Component:
export const Opts = (props) => {
return (
<select name={props.name}>
{props.options.map((x) => {
return (
<option key={x.key} value={x.value}>{x.text}</option>
)
})}
</select>
)
};
Result is that the HTML looks the same (which does not say that much I guess), ToppingsA is picked up in the values (on onSubmit) and ToppingsB is not.
I can't figure out what am I missing here and your help would be very much appreciated.
Thanks in advance,
Bert

If you are using render-props for toppingsB then the Field component prop should not be type "select" as the children of Field will be a function and not multiple tags.
It also looks like you are not letting your form know of any changes that occur inside the child component. Try passing the Opts component an onChange function as a prop:
<Opts
options={options}
name={input.name}
onChange={ (value:string) => input.onChange(value)}
/>

#S.Taylor, Thanks for your help!! It works.
As a reference the working code:
The Field in Parent Component:
<Field name="toppingsB" options={eatOptions} >
{ ({input, meta, options}) => {
return (
<Opts
options={options}
name={input.name}
onChange={ (value) => input.onChange(value)}
/>
)
}}
</Field>
And the code in the Child Component:
export const Opts = (props) => {
return (
<select name={props.name} onChange={props.onChange}>
{props.options.map((x) => {
return (
<option key={x.key} value={x.value}>{x.text}</option>
)
})}
</select>
)
};

Related

Reset a specific Input field on ant design form

How do I reset a specific input field, in this instance, title input field?
I know how to reset all fields (using form.resetFields()) but not a specific field on ant design
<Form.Item name='Title' label='Title'>
<Input value={title} onChange={handleChange('title')} />
</Form.Item>
<Form.Item name='Category' label='Category'>
<Select
placeholder='Select a category'
style={{ width: 420 }}
onChange={handleCategoryChange}
>
{categories.length > 0 &&
categories.map((c) => (
<Option key={c._id} value={c._id}>
{c.name}
</Option>
))}
</Select>
</Form.Item>
const handleCategoryChange = (e) => {
///reset Title field
};```
You are not showing the whole form, but since you are using form.resetFields() I am assuming that you are specifying some initialValues in your form tag. Let us say you have something similar to
const initValues = {
Title: "Foo",
Category: 123,
...
}
<Form
initialValues={initvalues}
...
/>
What form.resetFields() does is set all field values back to their initialValues. What you want, then, is to reset only the title field to its initial value.
This can then be accomplished by
const handleCategoryChange = (e) => {
form.setFieldsValue( { Title: initValues.Title } )
}

Setting default value for select field in redux form

Want to make a default option show up but doesn't seem to work no matter what I try
Already tried looking online but can't find anything that works
<Field
name="product_group"
component={renderSelectField}
label='Product Group'
defaultValue={{label: "RT", value: "RT"}}
options={this.state.options}
placeholder="Select Product Group"
multi={false}
/>
this is for rendering
export const renderSelectField = ({input, options, components, label, placeholder, disabled, multi, type, meta: {touched, error}}) => (
<div>
<label>{label}</label>
<div>
<Select
value={input.value}
onChange={input.onChange}
onBlur={() => input.onBlur(input.value)}
options={options}
components={components}
placeholder={placeholder}
onBlurResetsInput={false}
onSelectResetsInput={false}
autoFocus
disabled={disabled}
isMulti={multi}
/>
{touched && error && <span>{error}</span>}
</div>
</div>
);
Try this.
class Select extends Component {
render() {
const { name, value, onChange, error, options } = this.props;
return (
<div className="form-group">
<select
className="form-control"
id={name}
name={name}
value={value}
onChange={onChange}
error={error}
>
{options.map(option => (
<option key={option.name} value={option._id}>
{option.name}
</option>
))}
</select>
{error && <div className="text-danger">{error}</div>}
</div>
);
}
}
export default Select;
import above class in your main component. I assume you have defined this select name as "paymentOption" in state. Define your state with default value of select.
state={
paymentOption: "Receive Money"
}
payOptions = [
{ _id: "Send Money", name: "Send Money", value: "Send Money" },
{ _id: "Receive Money", name: "Receive Money", value: "Receive Money"
},
];
<Select name="paymentOption"
onChange={e =>
this.setState({
paymentOption: e.currentTarget.value
})
}
value={paymentOption} // const {paymentOption} = this.state
options={this.payOptions}
error={error}
cssClass={styles.selectList}
/>
If you put value={input.value || defaultValue } in renderSelectField component you can get it to work, but for me the it appears in the value object only after the field is touched.

Redux-form: Can't get children from Field component

I'm trying to render a select, and want to be able to do something like this to add options:
<Field component={RenderSelect} name="subjects" label="Subjects">
<option value="maths">Maths</option>
<option value="english">English</option>
</Field>
I have a createRenderer function:
const createRenderer = render => ({input, name, label, children}) => {
return (
<div key={name}>
<label htmlFor={name}>{label}</label>
{render(input, name, children)}
</div>
)
},
and my RenderSelect looks like this:
const RenderSelect = createRenderer((input, name, label, children) => {
return (
<select name={name} {...input}>
{children}
</select>
)
})
I was under the impression that I could just destructure the children prop off the Field like I do for input, name, label, etc, although this does not seem to work. When I run the code, no options appear in my select, and an inspection of the DOM verifies that there are no options. Any help would be much apprecited. Thanks in advance!
You just need to remove your label parameter
const RenderSelect = createRenderer((input, name, children) => {
return (
<select name={name} {...input}>
{children}
</select>
)
});

How to dynamically build a redux form?

I'm looking to learn how to dynamically build a redux-form. Idea being, the component called, componentDidMount goes at fetches a list of items from the server and inserts them in the store, store.my_items:
{'item1', 'itemB', 'itemZZZ', etc...}
Now that these items are in the store, I want to build the redux-form with a Field for each item in store.my_items.
Example, several of these dynamically created:
<div>
<label>ItemXXX</label>
<div>
<label>
<Field name="ItemXXX" component="input" type="radio" value="1" />
{' '}
1
</label>
<label>
<Field name="ItemXXX" component="input" type="radio" value="2" />
{' '}
2
</label>
</div>
</div>
With react, redux-form, what would be the right way to approach building this type of dynamic redux form?
Thank you
I am following this approach for building a form dynamically. I am just declaring metadata of form fields (type, placeholder, the unique name of each field, etc.) like this:
const fields = [
{ name: 'name', type: 'text', placeholder: 'Enter Name' },
{ name: 'age', type: 'number', placeholder: 'Enter age' },
{ name: 'email', type: 'email', placeholder: 'Enter Email' },
{ name: 'employed', type: 'checkbox' },
{
name: 'favouriteColors',
type: 'select',
options: [
{ label: 'Red', value: 'red' },
{ label: 'Yellow', value: 'yellow' },
{ label: 'Green', value: 'green' },
],
},
]
Now, I am just iterating over these fields and rendering input for each field like the way I have done in renderField component given below. My general form component looks like this:
import React from 'react'
import { Field, reduxForm } from 'redux-form/immutable'
const renderField = ({ input, field }) => {
const { type, placeholder } = field
if (type === 'text' || type === 'email' || type === 'number' || type === 'checkbox') {
return <input {...input} placeholder={placeholder} type={type} />
} else if (type === 'select') {
const { options } = field
return (
<select name={field.name} onChange={input.onChange}>
{options.map((option, index) => {
return <option key={index} value={option.value}>{option.label}</option>
})}
</select>
)
} else {
return <div>Type not supported.</div>
}
}
const SimpleForm = ({ handleSubmit, fields }) => {
return (
<div>
{fields.map(field => (
<div key={field.name}>
<Field
name={field.name}
component={renderField}
field={field}
/>
</div>
))}
<div onClick={handleSubmit}>Submit</div>
</div>
)
}
export default reduxForm({
form: 'simpleForm'
})(SimpleForm)
and passing fields to SimpleForm component like this:
<SimpleForm fields={fields} onSubmit={() =>{}}/>
Now it's your choice that you want to fetch fields like this from the server or just want to fetch only items and make fields like this (by passing item as the name of field) on the frontend.
By using this approach, I am able to re-use template based on given type.
If someone has a better approach to make a form dynamically, then I would love to learn that too.
Edit:
If we have to pass form name dynamically, then a small change will be required:
export default reduxForm({})(SimpleForm)
and we can pass form name while this component like this:
<SimpleForm form={'simpleForm'} fields={fields} onSubmit={() =>{}} />
you can create a component form and use it in a .map() function and pass formName as props like this...
render() {
return(
<div>
{yourDataFromServer.map((item, index) =>
<ReduxFormComp
form={`${FORM_NAMES.SIMPLE_FORM}__${index}`}
initialValues={item}
/>
)}
</div>
);
}
NOTE: when you use from initialValues for init your form, all field name should equal to your data.

ReactJS: How to wrap react-select in redux-form field?

I am working on react-select library and facing some issues, I am using redux-form library and importing <Field /> component from it. So that I can submit the values via form to service.
Below mentioned code works fine, when I use default <Select> from react-select. I can able to select the values from the drop down and the value will be selected even on focus out the value will remain. But selected value is not submitting via form due to redux-form that's why I am wrapping <Select /> component and using with <Field name="sample" component={RenderSelectInput} id="sampleEX" options={options} />
import React from 'react';
import Select from 'react-select';
import RenderSelectInput from './RenderSelectInput'; // my customize select box
var options = [{ value: 'one', label: 'One' }, { value: 'two', label: 'Two' }];
class SelectEx extends React.Component {
constructor() {
super();
this.state = { selectValue: 'sample' }
this.updateValue = this.updateValue.bind(this);
}
updateValue(newValue) {
this.setState({ selectValue: newValue })
}
render() {
return (
<div>
<Select name="select1" id="selectBox" value={this.state.selectValue} options={options} onChange={this.updateValue}/>
//This works but value won't submit ...
<Field name="sample" component={RenderSelectInput} id="sampleEX" options={options} />
//For this, selected value vanishes once I come out of component.
</div>
)
}
}
export default SelectEx;
But when I use with my customized select (I am wrapping the to submit the value from form) the <Select> component can be visible in UI even the values also. But unable to select the value from dropdown ..., If I select also it displays in the <Select> box but on focus out it vanishes. Please help me ...
RenderSelectInput component:
import React from 'react';
import {Field, reduxForm} from 'redux-form';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
const RenderSelectInput = ({input, options, name, id}) => (
<div>
<Select {...input} name={name} options={options} id={id} />
</div>
)
export default RenderSelectInput;
When using react-select with redux-form, you'll need to change the default behavior of onChange and onBlur method and call redux-form's onChange and onBlur method respectively.
So, Try this:
const RenderSelectInput = ({input, options, name, id}) => (
<Select
{...input}
id={id}
name={name}
options={options}
value={input.value}
onChange={(value) => input.onChange(value)}
onBlur={(value) => input.onBlur(value)}
/>
)
and use the above component like
<Field component={RenderSelectInput} />
Calling redux-form's onBlur method when focus is removed from the Select field will prevent loss of value.
Here this worked for me,
import React, { Component } from 'react';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
export default class RenderSelectInput extends Component {
onChange(event) {
if (this.props.input.onChange && event != null) {
this.props.input.onChange(event.value);
} else {
this.props.input.onChange(null);
}
}
render() {
const { input, options, name, id, ...custom } = this.props;
return (
<Select
{...input}
{...custom}
id={id}
name={name}
options={options}
value={this.props.input.value || ''}
onBlur={() => this.props.input.onBlur(this.props.input.value)}
onChange={this.onChange.bind(this)}
/>
);
}
}
this was extracted from here: https://ashiknesin.com/blog/use-react-select-within-redux-form/
Use this which works perfectly and it also handles redux form validation.
import React, {Component} from 'react';
import Select from 'react-select';
import {FormGroup} from "reactstrap";
class CustomSelect extends Component {
render() {
const {meta: {touched, error}} = this.props;
const className = ` form-group mb-3 ${touched && error ? 'has-danger' : '' }`;
return (
<FormGroup>
<Select
{...this.props}
value={this.props.input.value}
onChange={(value) => this.props.input.onChange(value)}
onBlur={() => this.props.input.onBlur(this.props.input.value)}
options={this.props.options}
placeholder={this.props.placeholder}
/>
<div className={className}>
<div className="text-help">
{touched ? error : ''}
</div>
</div>
</FormGroup>
);
Use the CustomSelect component in redux form field component as
<Field
name='country_name'
options={this.state.countries}
component={CustomSelect}
placeholder="Select your country"
/>
I had to call the onBlur without any argument. The issue with Hardik's answer was, it was not working in iPad (May be also in other iOS or touch devices. I was unable to check).
The onBlur event is automatically triggered along with the onChange event in iPad. It caused the select value to reset to its initial value. So I had to call onBlur method like this,
onBlur={(value) => input.onBlur()}
const RenderSelectInput = ({input, options, name, id}) => (
<Select
{...input}
id={id}
name={name}
options={options}
value={input.value}
onChange={(value) => input.onChange(value)}
onBlur={(value) => input.onBlur()}
/>
)
and used as,
<Field component={RenderSelectInput} />
Try setting onBlurResetInput property to false.
Something like.
const SelectInput = ({input: { onChange, value }, options, name, id}) => (
<Select
name={name}
value={value}
options={options}
onChange={onChange}
onBlurResetsInput={false}
/>
)
Hope this helps!

Resources