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>;
Related
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;
I'm trying to use Form.Select from semantic ui react to build a dropdrown. I'm sending an array of objects with the following properties to fill dropdown options:
DelivererForDropdown = {
key: deliverer.id,
value: deliverer.id,
text: deliverer.userName,
id: deliverer.id
};
The problem I'm facing is that the handleChange event from Formik is not updating my selection. I'm using this event for other inputs and it works well.
<Form.Select
type="text"
placeholder="Repartidor"
name="deliverer"
options={deliverersForDropdown}
search
onChange={handleChange}
error={errors.deliverer}
value={values.deliverer}
/>
You can use Formik function setFieldValue() to manually set the value in the handler onChange():
<Formik
enableReinitialize={true}
initialValues={initialValues}
onSubmit={async (values, { resetForm }) => {
console.log(JSON.stringify(values, null, 2));
}}
>
{({ isSubmitting, values, setFieldValue, setTouched, errors }) => {
return (
<FormikForm>
<Form.Select
type="text"
placeholder="Repartidor"
name="deliverer"
options={deliverersForDropdown}
search={true}
onChange={(event, { value }) => {
setFieldValue("deliverer", value);
setTouched("deliverer", true);
}}
error={errors.deliverer}
value={values.deliverer}
/>
<button type="submit" disabled={isSubmitting}>
SUBMIT
</button>
</FormikForm>
);
}}
</Formik>
Live Demo
Alternatively you can use a 3rd party that automatically binds, for example formik-semantic-ui
I need to create the below field with multiple checkboxes. I need the following validations:
At least one of them should be selected
If its the other field user need to add the text to the input field
I need to write a yup validation to achieve this.
At least one of them should be selected:
This is how you'll do that logic using yup validation rules
const validationSchema = yup.object().shape({
assets: yup.array().min(1).of(yup.string().required()).required(),
});
If its the other field user need to add the text to the input field:
You can also do this logic using Formik but I suggest you to create a local state for "assets" and if you wanna create new asset using text field just push that new asset in assets local field and then it will automatically render that new asset on the screen.
And then you can checked or unchecked that new asset. I hope you got the point.
This is the code snippet you can check!
import { useState } from "react";
import * as yup from "yup";
import { FieldArray, Formik } from "formik";
const initialValues = {
assets: [],
};
const validationSchema = yup.object().shape({
assets: yup.array().min(1).of(yup.string().required()).required(),
});
function Form() {
const [newAsset, setNewAsset] = useState("");
const [assets, setAssets] = useState([
"Property",
"Motor Vehicles",
"Financial Assets",
]);
const handleAddNewAsset = () => {
if (newAsset) {
setAssets([...assets, newAsset]);
setNewAsset("");
}
};
return (
<>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values) => {
console.log("form values if validation succeed: ", values);
}}
>
{(props) => (
<form onSubmit={props.handleSubmit}>
<FieldArray
name="assets"
render={(arrayHelpers) => (
<>
{assets.map((asset, idx) => (
<label className="form-check-label my-1">
<input
name={`assets.${idx}`}
type="checkbox"
className="form-check-input"
value={asset}
checked={props.values.assets.includes(asset)}
onChange={(e) => {
if (e.target.checked) {
arrayHelpers.push(asset);
} else {
const index = props.values.assets.indexOf(asset);
arrayHelpers.remove(index);
}
}}
/>
<span className="mx-2">{asset}</span>
</label>
))}
</>
)}
/>
<input
type="text"
value={newAsset}
onChange={(e) => setNewAsset(e.target.value)}
/>
<button type="button" onClick={handleAddNewAsset}>
Others (specify)
</button>
</form>
)}
</Formik>
</>
);
}
I hope it will help.
Thanks!
For Question 1 - At least one of them should be selected
Try below validationSchema
const validationSchema = Yup.object({checked: Yup.array().min(1, 'Select atleast one option of your interest')
});
in FormIk use initialValues like below:
<Formik initialValues={{checked: []}} />
For Question 2 - Other field validation you can check yup api documents.
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'