Having trouble manually adding 'Other' option in React. What gives? - javascript

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'

Related

Handle Reset for specific field formik

I am new to react and I have just started using Formik
I like how simple it makes making forms and handling forms in react.
I have created multiple custom fields using formik, I am putting the react-select field I created as an example here.
import { ErrorMessage, Field } from "formik";
import React from "react";
import Select from 'react-select'
const SelectInput = (props) => {
const { label, name, id,options, required, ...rest } = props;
const defaultOptions = [
{label : `Select ${label}`,value : ''}
]
const selectedOptions = options ? [...defaultOptions,...options] : defaultOptions
return (
<div className="mt-3">
<label htmlFor={id ? id : name}>
{label} {required && <span className="text-rose-500">*</span>}
</label>
<Field
// className="w-full"
name={name}
id={id ? id : name}
>
{(props) => {
return (
<Select
options={selectedOptions}
onChange={(val) => {
props.form.setFieldValue(name, val ? val.value : null);
}}
onClick = {(e)=>{e.stopPropagation}}
{...rest}
// I want someting like onReset here
></Select>
);
}}
</Field>
<ErrorMessage
name={name}
component="div"
className="text-xs mt-1 text-rose-500"
/>
</div>
);
};
export default SelectInput;
This is the usual code I use for submitting form as you can see I am using resetForm() method that is provided by formik, I want to attach the reseting logic in on submit method itself.
const onSubmit = async (values, onSubmitProps) => {
try {
//send request to api
onSubmitProps.resetForm()
} catch (error) {
console.log(error.response.data);
}
};
If you want to reset the selected value after the form is submitted, you need to provide a controlled value for the Select component.
The Formik Field component provides the value in the props object, so you can use it.
For example:
SelectInput.js
import { ErrorMessage, Field } from 'formik';
import React from 'react';
import Select from 'react-select';
const SelectInput = ({ label, name, id, options, required, ...rest }) => {
const defaultOptions = [{ label: `Select ${label}`, value: '' }];
const selectedOptions = options ? [...defaultOptions, ...options] : defaultOptions;
return (
<div className='mt-3'>
<label htmlFor={id ? id : name}>
{label} {required && <span className='text-rose-500'>*</span>}
</label>
<Field
// className="w-full"
name={name}
id={id ? id : name}
>
{({
field: { value },
form: { setFieldValue },
}) => {
return (
<Select
{...rest}
options={selectedOptions}
onChange={(val) => setFieldValue(name, val ? val : null)}
onClick={(e) => e.stopPropagation()}
value={value}
/>
);
}}
</Field>
<ErrorMessage name={name} component='div' className='text-xs mt-1 text-rose-500' />
</div>
);
};
export default SelectInput;
and Form.js
import { Formik, Form } from 'formik';
import SelectInput from './SelectInput';
function App() {
return (
<Formik
initialValues={{
firstName: '',
}}
onSubmit={async (values, { resetForm }) => {
console.log({ values });
resetForm();
}}
>
<Form>
<SelectInput
name='firstName'
label='First Name'
options={[{ label: 'Sam', value: 'Sam' }]}
/>
<button type='submit'>Submit</button>
</Form>
</Formik>
);
}
export default App;
Therefore, if you click the Submit button, value in the Select component will be reset.
You can also make a useRef hook to the Fromik component and then reset the form within the reset function without adding it as a parameter to the function.
https://www.w3schools.com/react/react_useref.asp
It's one of the really nice hooks you'll learn as you progress through React :)
So if I understood you correctly you want to reset a specif field value onSubmit rather than resetting the whole form, that's exactly what you can achieve using actions.resetForm().
Note: If nextState is specified, Formik will set nextState.values as the new "initial state" and use the related values of nextState to update the form's initialValues as well as initialTouched, initialStatus, initialErrors. This is useful for altering the initial state (i.e. "base") of the form after changes have been made.
You can check this in more detail here.
And here is an example of resetting a specific field using resetForm() whereby you can see as you input name, email and upon submit only email field will get empty using resetForm.
import "./styles.css";
import React from "react";
import { Formik } from "formik";
const initialState = {
name: "",
email: ""
};
const App = () => (
<div>
<h1>My Form</h1>
<Formik
initialValues={initialState}
onSubmit={(values, actions) => {
console.log(values, "values");
actions.resetForm({
values: {
email: initialState.email
}
});
}}
>
{(props) => (
<form onSubmit={props.handleSubmit}>
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.name}
name="name"
/>
<br />
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.email}
name="email"
/>
<br />
<br />
{props.errors.name && <div id="feedback">{props.errors.name}</div>}
<button type="submit">Submit</button>
</form>
)}
</Formik>
</div>
);
export default App;

Formik - Select first option in dropdown list when component gets loaded

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.

React function component parent state change does not re-render child component

I have a parent react function component (Products) which shows a list of products and with a state productInfo which is passed as a prop to the child component (AddEditProductModal)
const Products = () => {
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
const [productInfo, setProductInfo] = useState({});
const showEditModal = async (currentProductInfo) => {
console.log('edit called for key ',currentProductInfo.key)
setIsEditModalVisible(true);
setProductInfo(prevProductInfo => {
return {...prevProductInfo, ...currentProductInfo};
});
};
useEffect(() => {
setIsEditModalVisible(false);
setProductInfo({})
}, []);
return (
<>
<AddEditProductModal
title="Edit Product"
visible={isEditModalVisible}
productInfo={productInfo}
onOk={handleOk}
onCancel={handleCancel}
onFinish={onFinish}
/>
//Table components with columns/actions per row go here
</>
);
};
export default Products;
The child component AddEditProductModal is an antd Modal/Popup which fills the form with prefilled values chosen for current product row as shown below.
const AddEditProductModal = ({ title, visible, productInfo, onOk, onCancel, onFinish }) => {
return (
<Modal
title={title}
visible={visible}
onOk={onOk}
onCancel={onCancel}
>
<Form
name="basic"
labelCol={{
span: 8,
}}
wrapperCol={{
span: 16,
}}
onFinish={onFinish}
initialValues = {productInfo}
>
<Form.Item
label="Key"
name="key"
>
<Input disabled={true} />
</Form.Item>
<Form.Item
label="Image"
name="image"
rules={[
{
required: true,
message: "Please input image!",
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Name"
name="name"
rules={[
{
required: true,
message: "Please input name!",
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Price"
name="price"
rules={[
{
required: true,
message: "Please input price!",
},
]}
>
<Input />
</Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form>
</Modal>
);
};
export default AddEditProductModal;
The productInfo is an object containing props as shown below:
{"key":19,"name":"Cooker","image":"https://.....d16bfa6d9c2e010cadc3fe6885448cbd.jpg_720x720q80.jpg","price":123}
When I click on any row's Edit button, the AddEditProductModal shows correct default values for the product. But when I click on another product/row, the AddEditProductModal still shows old values even though the productInfo state (seen in the profiler) has changed. Basically, the productInfo state has changed in the parent but the child has not re-rendered is what I am thinking.
Can anyone help why the modal shows the info on the first click but second time, fails to re-render and shows old product info ?
Yes, you are right! First, antd Form's API initialValues only works when it is initialized or reset. Second, antd Modal won't destroy after the close. So there is the result you said.
method A: do what you said,
useEffect(() => {
form.setFieldsValue(productInfo);
}, [productInfo]);
method B: destroyOnClose property of Modal set true will also solve your issue, but this is not a good choice!
const AddEditProductModal = ({ title, visible, productInfo, onOk, onCancel, onFinish }) => {
return (
<Modal
title={title}
visible={visible}
onOk={onOk}
onCancel={onCancel}
+destroyOnClose+
>
...
I got the answer to my problem. Rather than react, this seemed like an issue with the antd Form component initialValues. Seems like the initialValues does not change and we need to explicitly update the values. Adding the following to my AddEditProductModal solved my issue:
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(productInfo);
}, [productInfo]);
Hope this helps some antd user some day.

Use select in Semantic UI with Formik

I'm usign Formik to validate some data fields with Semantic UI in React. It works fine with input fields but doesn't work with selectors.
How it works with input fields:
import { Formik, Form, Field } from 'formik';
import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
...
const initialValues = {
name: ''
};
const requiredErrorMessage = 'This field is required';
const validationSchema = Yup.object({ name: Yup.string().required(requiredErrorMessage) });
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field name="name" as={Input} placeholder="name" /> // here is the input
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
</Formik>;
However, when the as={Input} is replaced by as={Select}, it doesn't work. The dropdown gets opened when I click on the selector, it shows the options (company1 and company2), I click on one of them but it does not work -> the value shown is still the placeholder value.
import { Formik, Form, Field } from 'formik';
import { Input, Button, Select, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field
name="name"
as={Select} // here is changed to Select
options={companyOptions} // the options
placeholder="name"
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
</Formik>;
The reason is because Formik passes an onChange function into the component. However, the Select (which is just syntactic sugar for dropdown https://react.semantic-ui.com/addons/select/) onChange prop uses a function of shape handleChange = (e: React.SyntheticEvent<HTMLElement>, data: SelectProps) => this.setState({value: data.value}) (https://react.semantic-ui.com/modules/dropdown/#usage-controlled) which is different than the conventional handleChange = (event: React.SyntheticEvent<HTMLElement>) => this.setState({value: event.target.value}) which formik uses.
So an easy solution would be to use the Formik function setFieldValue.
In your code, the implementation would be something like this:
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched, setFieldValue, values }) => {
const handleChange = (e, { name, value }) => setFieldValue(name, value));
return (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Field
name="name"
as={Select} // here is changed to Select
options={companyOptions} // the options
placeholder="name"
onChange={handleChange}
value={values.name}
/>
<div>{touched.name && errors.name ? errors.name : null}</div>
</Grid.Column>
</Grid>
</CreationContentContainer>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
</Form>
)}
}
</Formik>;
Here's a somewhat working version using your code. The fields update (verified with a console.log), but the submit button isn't working 🤷‍♂️: https://codesandbox.io/s/formik-and-semanticui-select-gb7o7
This is a somewhat incomplete solution and does not handle the onBlur and other injected formik functions. It also defeats the purpose of Formik somewhat. A better solution would be to create a HOC that wraps semantic ui components to use Fromik correctly: https://github.com/formium/formik/issues/148.
And lastly, you can skip the Semantic UI Select and use the Semantic UI HTML controlled select: https://react.semantic-ui.com/collections/form/#shorthand-field-control-html. Notice the select vs Select. This will work natively with Formik without the need to pass in onChange and value.
I recently created a binding library for Semantic UI React & Formik.
Link: https://github.com/JT501/formik-semantic-ui-react
You just need to import Select from the library and fill in name & options props.
// Import components
import { Select, SubmitButton } from "formik-semantic-ui-react";
const companyOptions = [ // the select's options
{ text: 'company1', value: 'company1' },
{ text: 'company2', value: 'company2' },
];
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<div>
<CreationContentContainer>
<Grid>
<Grid.Column>
<Label>Company name</Label>
<Select
name="name"
selectOnBlur={false}
clearable
placeholder="name"
options={companyOptions}
/>
</Grid.Column>
</Grid>
</CreationContentContainer>
<SubmitButton>
Create company
</SubmitButton>
</div>
</Form>
)}
</Formik>;

In React how do you handle the onChange of several select dropdowns

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;

Categories