I have a form which has several select as Dropdown components and I want the onChange handler to handle them individually. How can I do that?
The Dropdown are dynamically generated according to the number of questions from the database, hence it'll be ridiculous to write an onChange handler for each Dropdown (e.g. 30 questions)
component
const Dropdown = props => {
const { title, id, onChange, noSelection, options, value } = props;
return (
<div className="form-group">
<label className="control-label" htmlFor={id}>
{title}
</label>
<select
className="form-control"
name={id}
id={id}
onChange={onChange}
value={value}
>
{noSelection && <option defaultValue="" />}
{options.map(option => (
<option value={option} key={option}>{option}</option>
))}
</select>
</div>
);
};
export default Dropdown;
class Form extends Component {
constructor() {
super();
this.state = {
noSelection: true,
value,
};
this.handleSelect = this.handleSelect.bind(this);
}
handleSelect(event) {
let target = event.target;
let value = target.value;
this.setState({
noSelection: !this.state.noSelection,
value,
});
}
render() {
return (
<div className="care-sub">
<Dropdown
title="Are a developer wanting to become a programmer?"
id="select-care-leaver"
onChange={this.handleSelect}
noSelection={this.state.noSelection}
options={['Yes', 'No']}
value={this.state.value}
/>
<Dropdown
title="Do you have experience with React"
id="select-care-estranged"
onChange={this.handleSelect}
noSelection={this.state.noSelection}
options={['Yes', 'No']}
value={this.state.value}
/>
</div>
);
}
}
export default Form
I expect each Dropdown to onChange individually when the user interact with them and without changing the other Dropdowns and without writing repeated handlers for each of them.
For dynamic handling of fields, I usually give the fields a name, and handle the onChange with:
this.setState({
formData: {
...this.state.formData,
[event.target.name]: event.target.value,
},
}
As long as you render the selects with those names, and assign their value from the state, all will work well.
Note - I keep it in an object, so I can just get all the form data at once with this.state.formData.
You can assign this same onChange handler to multiple fields and they'll all work. (Note, this also works for input fields, and anything that uses .value)
It is about time your Dropdown evolves from a functional component to a react component. Since you are re-using your Dropdown component you would need to create the onChange handler for it just once as a member function to Dropdown.
Then, everytime a Dropdown renders, it will use the same member function in a different this context.
Like so:
class Dropdown extends React.Component{
onChange (e) {
//do something based on props and value
}
render(){
const { title, id, noSelection, options, value } = this.props;
return (
<div className="form-group">
<label className="control-label" htmlFor={id}>
{title}
</label>
<select
className="form-control"
name={id}
id={id}
onChange={this.onChange} // use the member function here
value={value}
>
{noSelection && <option defaultValue="" />}
{options.map(option => (
<option value={option} key={option}>{option}</option>
))}
</select>
</div>
);
}
};
export default Dropdown;
Related
I have the following code which has a dropdown list with few values.
I want that when the component gets loaded, it selects the first option and let me submit right after.
I tried with the line below but no luck:
formik.setFieldValue(name, value);
Here you have the code:
import { ErrorMessage, Field, Form, Formik } from 'formik';
import { get } from 'lodash-es';
import React, { useEffect } from 'react';
import * as Yup from 'yup';
const DropdownListInput = (props) => {
useEffect(() => {
const firstOptionValue = get(props.children, '[0].props.value', '');
console.log({
name: props.field.name,
value: props.value,
firstOptionValue,
});
if (props.value === '' && firstOptionValue !== '') {
props.formik.setValues({
[props.field.name]: firstOptionValue,
});
}
}, []);
return (
<select
value={props.value}
onChange={(e) => props.handleChange(e.target.value)}
>
{props.children.map(({ props: { value, children: text } }, index) => (
<option value={value} key={index}>
{text}
</option>
))}
</select>
);
};
export default () => {
return (
<Formik
initialValues={{
email: '',
}}
validationSchema={Yup.object().shape({
email: Yup.string()
.required('Email is required.')
.email('Email is invalid.'),
})}
onSubmit={(values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
}}
enableReinitialize
validateOnMount
>
{(formik) => {
return (
<Form>
<div>
<Field
component={DropdownListInput}
formik={formik}
name="email"
value={formik.values.email}
handleChange={(value) => {
console.log(value);
formik.setFieldValue('email', value);
}}
>
<option value="bill.gates#microsoft.com">Bill Bates</option>
<option value="steve.jobs#apple.com">Steve Jobs</option>
<option value="elon.musk#tesla.com">Elon Musk</option>
</Field>
<ErrorMessage name="email">
{(error) => <div style={{ color: '#f00' }}>{error}</div>}
</ErrorMessage>
</div>
<input type="submit" value="Submit" disabled={!formik.isValid} />
</Form>
);
}}
</Formik>
);
};
Here you have the Stackblitz you can play with:
https://stackblitz.com/edit/react-formik-yup-example-uhdg-dt6cgk?file=Registration.js
Is there any way to select the first option automatically when the component gets loaded?
Requirements:
I need the Submit button to be enabled automatically.
Using initialValues is not an option for me because the dropdown is in the middle of a more complex context then it is the same dropdown who has to trigger the setting of that value.
If you want you can post your forked Stackblitz.
Thanks!
You should be able to just add the initial value as the default value. Since that would be selected by default, the initial value can reflect the same, and be changed on change.
initialValues={{
email: 'bill.gates#microsoft.com',
}}
This requirement is very straightforward where you always want to have a selected option in your dropdown, defaulting to the first one. To do that you have to set select attribute of tag just for first one. It's a pure HTML thing.
you can find the reference here:
<option value="bill.gates#microsoft.com" select>
Bill Bates
</option>
https://stackblitz.com/edit/react-formik-yup-example-uhdg-bgzdh7?file=Registration.js
Another scenario would be you want to preserve the user selection on rerenders, to do that you can rely on setting up initialValues to what user has selected.
In that way, if any selection is there reflect that or else default to the very first item of the dropdown.
I have a datalist that I want to be pre-populated based on the prop I am passing down. It correctly sets the value, but does not allow the dropdown value to be changed and basically disables it. Any ideas on how to fix this?
<input list="facilitators" name="facilitator" id="facilitatorsInput" value={this.props.breakout.facilitatorId}></input>
<datalist id="facilitators">
{
this.state.facilitators.map((facilitator) => <option value={facilitator.id} >{facilitator.firstName} {facilitator.lastName}</option>)
}
</datalist>
It looks like you are setting the value, but you have no way to change the value. Your <input /> needs an onChange handler.
Here's a working example: https://codesandbox.io/s/xrv9q019zo
const options = ["banana", "tomato", "apple"];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
input: "banana"
};
}
render() {
return (
<div className="App">
<input
onChange={e => {
this.setState({ input: e.target.value });
}}
value={this.state.input}
list="stuff"
/>
<datalist id="stuff">
{options.map(opt => (
<option value={opt} />
))}
</datalist>
</div>
);
}
}
If you are getting the default value from the parent component, you may want to pass the onChange handler from the parent and manage the state there.
i am developing a form in reactjs using formik plugin plugin link. when i submit form i am getting text fields values but dropdown values are coming empty...
this is my dropdown select
<div className="form-group">
<Field component="select" id="category" name="category" value={this.state.value} className={"form-control"} onChange={ this.handleChange }>
<option value="lokaler">Lokaler</option>
<option value="jobb">Jobb</option>
<option value="saker-ting">Saker & ting</option>
<option value="evenemang">Evenemang</option>
</Field>
</div>
handle submit function
export default withFormik({
enableReinitialize: true,
mapPropsToValues({ category }) {
return {
category: category || ''
}
},
handleSubmit(values, { setStatus, setErrors }){
console.log("data is this: ");
console.log(values); //here i am getting all form fields values except select value returning empty value
console.log("category: "+values.category);// here i always get empty value but not getting selected value
}
i am getting all text fields values in handle submit function but i am not able to get dropdown selected value....kindly tell me how to get dropdown select value in handle submit function ?
I also faced this problem yesterday. My problem was to submit form that contains ant design dropdown. I finally make it work after hours of:
revisiting the Formik Docs
watch Andrew Mead's video Better React Form with Formik again.
also viewing Jared Palmer's Working with 3rd-party inputs #1: react-select
The doc said you need to set onChange, onBlur events to setFieldValue, setFieldTouched props respectively like 3rd-party input example:
<MySelect
value={values.topics}
onChange={setFieldValue}
onBlur={setFieldTouched}
error={errors.topics}
touched={touched.topics}
/>
So to my problem, I need to change a bit:
<Select
{...field}
onChange={(value) => setFieldValue('fruitName', value)}
onBlur={()=> setFieldTouched('fruitName', true)}
value={values.fruitName}
...
>
...
</Select>
Hope this will help.
Here is my CodeSandbox
A more robust way to handle select components whilst using Formik would be to also use Jed Watsons react-select component. The two work together nicely and abstract away a lot of the boilerplate you would normally need to implement to get the component working seamlessly with Formik.
I have forked a simple example from Jared Palmer's react-select / Formik example on codesandbox and adjusted it to reflect your code above.
import "./formik-demo.css";
import React from "react";
import { render } from "react-dom";
import { withFormik } from "formik";
import Select from "react-select";
import "react-select/dist/react-select.css";
const options = [
{ value: "lokaler", label: "Lokaler" },
{ value: "jobb", label: "Jobb" },
{ value: "saker-ting", label: "Saker & ting" },
{ value: "evenemang", label: "Evenemang" }
];
const MyForm = props => {
const {
values,
handleChange,
handleBlur,
handleSubmit,
setFieldValue
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="myText" style={{ display: "block" }}>
My Text Field
</label>
<input
id="myText"
placeholder="Enter some text"
value={values.myText}
onChange={handleChange}
onBlur={handleBlur}
/>
<MySelect value={values.option} onChange={setFieldValue} />
<button type="submit">Submit</button>
</form>
);
};
class MySelect extends React.Component {
handleChange = value => {
// this is going to call setFieldValue and manually update values.topcis
this.props.onChange("option", value);
};
render() {
return (
<div style={{ margin: "1rem 0" }}>
<label htmlFor="color">Select an Option </label>
<Select
id="color"
options={options}
onChange={this.handleChange}
value={this.props.value}
/>
</div>
);
}
}
const MyEnhancedForm = withFormik({
mapPropsToValues: props => ({
myText: "",
option: {}
}),
handleSubmit: (values, { setSubmitting }) => {
console.log(values);
}
})(MyForm);
const App = () => <MyEnhancedForm />;
render(<App />, document.getElementById("root"));
There are a few gotchas, you have to include the react select css for the component to display properly
import "react-select/dist/react-select.css";
the function mapPropsToValues option field should be initialised to an object
mapPropsToValues: props => ({
myText: "",
option: {}
Finally here is a link to the codesandbox example
I'm having a minor problem with respect to the functionality of my React Component illustrated below. Specifically, I would like for the user to be able to enter (via an input field) the poll category, if not listed under the options of the select component. This happens when the user selects the 'Other' option, which renders said input field as seen below. The issue I'm having is when the user starts entering a new category (triggering the onChange listener), this.setState results in 'category' no longer being 'Other' and, thus, the input field is no longer rendered. As such, it is impossible to enter another category. Below is the relevant code.
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Button, Form, Grid, Header, Icon } from 'semantic-ui-react';
class NewPoll extends Component {
state = {
title: '',
category: '',
choice: '',
choices: [],
};
onChange = e =>
this.setState({
[e.target.name]: e.target.value,
});
onSelect = e =>
this.setState({
category: e.target.value,
});
onAddChoice = e => {
//...
};
onRemoveChoice = id => {
//...
};
onPollSubmit = e => {
//...
};
render() {
const { title, category, choices, choice } = this.state;
const categories = [
'Sport',
'Travel & Tourism',
'Education',
'Technology',
'Automotive',
'Other',
];
// Preview and edit poll before saving
const shouldShowPreview = () => {
if (title && choices.length > 0) return true;
else return false;
};
return (
<Fragment>
<Grid.Row>
<Grid.Column>
<Header size="large" textAlign="center">
Create New Poll
</Header>
</Grid.Column>
</Grid.Row>
<Grid.Row columns={shouldShowPreview() ? 2 : 1}>
<Grid.Column>
<Form style={styles.form} onSubmit={this.onPollSubmit}>
<Form.Field>
...
</Form.Field>
<Form.Field
placeholder="Category"
label="Poll Category"
control="select"
value={category}
onChange={this.onSelect}
>
<option disabled>Category</option>
{categories.map((pollCategory, index) => (
<option value={pollCategory} key={index}>
{pollCategory}
</option>
))}
</Form.Field>
{category === 'Other' && (
<Form.Field>
<label>If "Other"</label>
<input
name="category"
value={category}
placeholder="Enter category"
onChange={this.onChange}
/>
</Form.Field>
)}
<Form.Field>
<label>Poll Choices</label>
<div style={styles.choice}>
<input
name="choice"
value={choice}
placeholder="Enter poll choice"
onChange={this.onChange}
/>
<Button className="add-choice" onClick={this.onAddChoice}>
<Icon style={styles.icon} name='add' size='large' />
</Button>
</div>
</Form.Field>
<Form.Field control={Button} type="submit">
Submit Poll
</Form.Field>
</Form>
</Grid.Column>
</Grid.Row>
</Fragment>
);
}
}
NewPoll.propTypes = {
addNewPoll: PropTypes.func.isRequired,
};
export default NewPoll;
This issue is being caused because you are using the same variable - category to do two things:
Store what the actual category is
Determine whether to show the additional textbox
You have two options:
Create a different variable to show the additional textbox. For example, something like:
const showCategoryTextBox = ...//whether category belongs in the categories list.
// and then use this to control the display of the textbox.
OR
Modify your condition like:
(category!== 'Sport' && category!=='Travel & Tourism' &&...)
// include all the values in your categories list except 'Other'
Sorry for the general title but I don't know what else to call it at this point. I have a form that uses widgets. Im using the react-jsonschema-form package. Id use the github for the project but I don't think this is bug so I want to check here first. I think it's just how to use this React thing question. Possibly Redux as well.
So...
I have these widgets for some of the form elements in my component.
dropdownWidget = (props, type, id) => {
return (
<div>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
multiFileWidget = (props) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={fileWidgetPopup}>
<input type="file" id="multiFileName" required={props.required} onChange={(event) => { props.onChange(event.target.value); this.getFileNames(); this.hideResultTables(event.target.id) }} multiple />
</OverlayTrigger>
<textarea id="multiFileNameList" className="form-control" rows="4" style={{ marginTop: "2%" }} readOnly />
</div>
)
}
dropdownTooltipWidget = (props, type, id, tooltip) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={tooltip} hidden>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
</OverlayTrigger>
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
They are stuffed in a object like so:
multiUISchema = {
file: {
'ui:widget': this.multiFileWidget,
classNames: "uiSchema",
},
convertTo: {
'ui:widget': (props) => this.dropdownWidget(props, "ConvertToTypes", "multiConvertTo"),
classNames: "uiSchema"
},
notification: {
'ui:widget': (props) => this.dropdownTooltipWidget(props, "NotificationType", "notification", websNotificationPopup),
classNames: "uiSchema"
}
}
Where things go bad!! This function is injected in the widgets onChange handler.
hideResultTables(targetId) {
debugger;
//disable Retrieve button
if (targetId === 'multiFileName' || targetId === 'multiConvertTo') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
this.setState({ disabled: true });
}
//leave Retrieve Button enabled
else if (targetId !== 'appIDs') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
}
}
Basically if I hit the first if statement which has the setState for disabled to True, my onChange doesn't seem to retain the selected dropdown data to the Form Data object. It's like setState for Disabled true, which is for a button unrelated to the field does something to in the reconciliation tree possibly and/or my form data never retains the selected field in the formdata object at all?? idk. The field just stays undefined in all console logs if it's a field that hits that first if statement with the setState in it. But that setState has nothing to do with the fields being selected. It's disabling a button further down the page is all. Im sure im missing some React lifecycle, reconciliation async something or other. Im thinking of just reduxing this all but this simple bool value seems like it should stay as local state to the component. Any thoughts on things I should be checking before i redux this simple bool for my form?
To Further clarify my question, why does setState in the injected function in onChange seem to disrupt the onChange value update for the field on the form in my Widget when I select a field. That's really what this is all about. We cannot figure out why this is happening.
footnote** The dropdown data in the form is redux store data. So im selecting store data in my onchange in the widgets. Not sure if that's relevant.