I made a reusable formik form, but I want to use ant select instead of formik select. the Error message is not working with ant design and I dont know how to configure that.
I need to show the error message when it's not validated. in console.log there is no problem it wworks perfect as i change the ant select. But the errortext does not shows
import React from "react";
import { Formik } from "formik";
import {Form} from 'antd'
import * as Yup from "yup";
import FormikController from "../components/Forms/FormikController";
const city = [
{ key: "option 1", value: "option1" },
{ key: "option 2", value: "option2" },
{ key: "option 3", value: "option3" },
{ key: "option 4", value: "option4" },
];
const Contact = () => {
const initialValues = {
whoYouAre: "",
email: "",
message: "",
};
const validationSchema = Yup.object({
whoYouAre :Yup.string().required(),
email :Yup.string().required()
});
return (
<Container className="contact">
<h5 className="mb-4">Contact</h5>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
// onSubmit={handleAddData}
validateOnMount
>
{(formik) => (
<Form className="w-100">
<FormikController
control="selectsearch"
options={city}
formik={formik}
name="whoYouAre"
label="Please Tell Us Who You Are"
/>
<FormikController
control="input"
type="text"
name="email"
label="Email"
/>
<FormikController
control="textarea"
name="message"
rows="8"
label="Message"
/>
<div>
<Button variant="primary" type="submit">
Send
</Button>
</div>
</Form>
)}
</Formik>
</Container>
);
};
export default Contact;
and here the resusable select that i used from ant design
import React from "react";
import { Form,Select } from "antd";
import { ErrorMessage } from "formik";
import TextError from "./TextError";
const { Option } = Select;
const SearchSel = (props) => {
const { label, name, options ,formik, ...rest } = props;
console.log(formik);
return (
<Form.Item className="mb-3">
<Select
showSearch
size="large"
optionFilterProp="children"
placeholder={label}
name={name}
onChange={(value) => formik.setFieldValue("whoYouAre", value)}
>
{options.map((option) => {
return (
<Option key={option.value} value={option.value}>
{option.key}
</Option>
);
})}
</Select>
<ErrorMessage name={name} component={TextError} />
</Form.Item>
);
};
export default SearchSel;
Your default state is "" isn't right I believe. You probably want null.
Secondly, your Select is not correctly bound to the form state. You need to add value.
Moreover, you also need to add defaultActiveFirstOption={false} as that may be immediately selecting the first item on the initial state.
Finally, and probably most importantly, probably need to bind the fields blur such that it sets the field as touched. ErrorMessage won't show the error unless it thinks the user touched the field:
<Select
showSearch
size="large"
optionFilterProp="children"
placeholder={label}
name={name}
value={formik.values.whoYouAre}
defaultActiveFirstOption={false}
onChange={(value) => formik.setFieldValue("whoYouAre", value)}
onBlur={() => formik.setFieldTouched("whoYouAre", true)}
>
Related
Since I want my variation form to be dynamic, I have used the concept of field array from react-hook-form. For the select field I am using react-select which is used for taking multiple values for a particular option from the user. This is how I have done
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import ReactSelect from "./Select";
import Input from "./Input";
import VariationPreview from "./VariationPreview";
const initialValues = {
variations: [
{
id: 1,
option: "Size",
values: [
{ id: 1, label: "SM", value: "sm" },
{ id: 2, label: "MD", value: "md" }
]
},
{
id: 2,
option: "Color",
values: [
{ id: 1, label: "Red", value: "red" },
{ id: 2, label: "Blue", value: "blue" }
]
}
]
};
const VariationSetup = () => {
const {
register,
control,
handleSubmit,
formState: { errors },
watch,
getValues
} = useForm({
defaultValues: initialValues ?? {}
});
const { fields, append, remove } = useFieldArray({
control, // control props comes from useForm (optional: if you are using FormContext)
name: "variations" // unique name for your Field Array
});
const variants = watch("variations");
console.log("variants", variants);
return (
<>
<p className="font-semibold mb-4 text-md">Options</p>
{fields.map((field, index) => {
return (
<React.Fragment key={field.id}>
<div className="flex mb-4" key={field.id}>
<div className="w-full md:w-4/12">
<Input
name={`variations.${index}.option`}
label="Option"
placeholder="Choose option"
options={VARIATION_OPTION}
helperText="Choose option that can be applied as variants for a product"
errors={errors}
register={register}
/>
</div>
<div className="w-full md:ml-4 md:w-8/12">
<ReactSelect
name={`variations.${index}.values`}
label=""
placeholder="Choose value"
options={[]}
helperText="You can choose multiple values"
wrapperClassName="mt-7"
errors={errors}
register={register}
isCreateable
/>
</div>
</div>
</React.Fragment>
);
})}
<button
className="bg-gray-200 p-3"
type="button"
onClick={() => append({ option: "", value: "" })}
>
Add option
</button>
<div className="divide-y"></div>
{/* PREVIEW */}
<VariationPreview variations={watch("variations")} />
</>
);
};
export default VariationSetup;
const VARIATION_OPTION = [
{ id: 1, label: "Size", value: "size" },
{ id: 2, label: "Color", value: "color" }
];
this is the react-select with hook-form binding
import { Controller, useForm } from "react-hook-form";
import Select from "react-select";
import CreatableSelect from "react-select/creatable";
export default function ReactSelect({
disabled,
label,
helperText,
name,
placeholder,
options,
defaultValue,
className,
labelClassName,
wrapperClassName,
isCreateable = false
}) {
const {
control,
formState: { errors }
} = useForm();
const getDefaultValue = (value) => {
if (value && value.length) {
return value[0];
} else return value;
};
return (
<div className={wrapperClassName ? wrapperClassName : ""}>
{/* TODO: Label as a separate component */}
<label
htmlFor={name}
className={
labelClassName
? labelClassName
: "block font-semibold mb-2 text-gray-700 text-sm tracking-wide uppercase"
}
>
{label}
</label>
<div className="mt-1 relative">
<Controller
name={name}
defaultValue={getDefaultValue(defaultValue)}
control={control}
render={({ field }) => {
const styles = errors[name] ? errorStyles : customStyles;
if (isCreateable) {
return (
<CreatableSelect
isMulti
{...field}
isDisabled={disabled}
placeholder={placeholder}
options={options}
styles={styles}
className={className}
/>
);
} else {
return (
<Select
{...field}
isDisabled={disabled}
placeholder={placeholder}
options={options}
styles={styles}
className={className}
/>
);
}
}}
/>
</div>
</div>
);
}
Here the problem is when I update values field the variations object does not get updated because of which I cannot update Variation Preview table. Also when if I add new option, the option key gets updated and is reflected in VariationPreview table but when I add values for that option the values object is shown empty. Could anyone point me where i did the mistake? I have a code in playground either
https://codesandbox.io/s/elegant-robinson-fiujlj?file=/src/Variation.jsx:0-2764
You call useForm inside the ReactSelect component. Now there are two unrelated forms. That's why variations object does not get updated.
You can pass the control down to ReactSelect and use it for Controller component.
And for errors handling:
const { errors } = useFormState({
control
});
I am trying to use useFieldArray for dynamic Select field of Material UI and react-hook-form. It works fine with TextField, but when used with Select it doesn't work..
What doesn't work..
Set defaultValue doesn't show in Select, but shows in TextField
Value selected on Select and inputted value in TextField doesn't come when click in Submit.
Here is the code of Select component
import * as React from "react";
import {
FormControl,
FormHelperText,
InputLabel,
MenuItem,
OutlinedInput,
Select,
SelectChangeEvent
} from "#mui/material";
import { useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
import Placeholder from "./Placeholder";
export interface IOptionTypes {
id: string;
label: string;
value: string;
}
interface IFormElementTypes {
name: string;
label: string;
required?: boolean;
defaultValue: string;
options: IOptionTypes[];
placeholder: string;
}
export default function MultiSelectField({
name,
label,
required,
defaultValue,
options,
placeholder
}: IFormElementTypes) {
const {
control,
register,
formState: { errors }
} = useFormContext();
// const defaultVal = control._defaultValues[name];
const [selectedVal, setSelectedVal] = useState<string[]>([]);
const handleChange = (event: SelectChangeEvent<typeof selectedVal>) => {
const {
target: { value }
} = event;
setSelectedVal(
// On autofill we get a stringified value.
typeof value === "string" ? value.split(",") : value
);
};
const labelText = `${label}${required ? "*" : ""}`;
const labelId = `multi-select-${name}`;
return (
<Controller
name={name}
defaultValue={defaultValue}
control={control}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel
shrink
sx={{ backgroundColor: "white", padding: "0px 2px" }}
error={!!errors[name]}
id={labelId}
>
{labelText}
</InputLabel>
<Select
{...field}
label={labelText}
labelId={labelId}
value={selectedVal}
displayEmpty
multiple
{...register(`${name}` as const)}
variant="outlined"
fullWidth
error={!!errors[name]}
input={
<OutlinedInput
id={`multi-select-${label}`}
label={`${label}${required ? "*" : ""}`}
/>
}
renderValue={
selectedVal.length > 0
? undefined
: () => <Placeholder>{placeholder}</Placeholder>
}
onChange={handleChange}
>
{options.map((option) => (
<MenuItem key={option.id} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText>{String(errors[name]?.message ?? "")}</FormHelperText>
</FormControl>
)}
/>
);
}
and How it is been called finally
import * as React from "react";
import {
useForm,
useFieldArray,
FormProvider,
SubmitHandler
} from "react-hook-form";
import { Button, Grid, Stack, Box } from "#mui/material";
import MultiSelectField, { IOptionTypes } from "./MultiSelectField";
import TextFieldElement from "./TextFieldElement";
type FormValues = {
items: { name: string; age: string }[];
};
const defaultValues = {
items: [{ name: "john", age: "10" }]
};
const names: IOptionTypes[] = [
{ id: "1", label: "John", value: "john" },
{ id: "2", label: "Smith", value: "smith" },
{ id: "3", label: "Julia", value: "julia" },
{ id: "4", label: "David", value: "david" }
];
export default function App() {
const methods = useForm<FormValues>({
defaultValues,
mode: "all"
});
const { control, formState, setError } = methods;
const { fields, append, remove } = useFieldArray({ name: "items", control });
const formSubmitHandler: SubmitHandler<FormValues> = async (
data: FormValues
) => {
console.log("Form values ", data);
};
return (
<Grid container>
<Grid item xs={12}>
<Stack direction="row" spacing={2} my={2}>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
onClick={() => {
append({ name: "", age: "" });
}}
>
Add Field
</Button>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
onClick={() => {
remove(fields.length - 1);
}}
>
Remove Field
</Button>
</Stack>
</Grid>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(formSubmitHandler)}>
<Grid item xs={12}>
<Stack spacing={2}>
{fields.map((field, index) => (
<Stack direction="row" spacing={2} key={field.id}>
<MultiSelectField
name={`items.${index}.name`}
label="Choose Name"
placeholder="Name"
defaultValue=""
options={names}
/>
<TextFieldElement
name={`items.${index}.age`}
label="Input Age"
placeholder="Age"
/>
</Stack>
))}
</Stack>
</Grid>
<Grid container my={2}>
<Grid item>
<Button
variant="outlined"
color="primary"
type="submit"
disableElevation
fullWidth
>
Save Changes
</Button>
</Grid>
</Grid>
</form>
</FormProvider>
</Grid>
);
}
As in the code above, I have set default values, which works for TextField but not for the SelectField
I wonder what is wrong in there..
Here is my Codesandbox, thanks for help.
You're going to want to make sure that you set the value prop on your Select inside of MultiSelectField to selectedVal:
<Select
{...field}
label={labelText}
labelId={labelId}
value={selectedVal}
displayEmpty
multiple
{...register(`${name}` as const)}
variant="outlined"
fullWidth
error={!!errors[name]}
input={
<OutlinedInput
id={`multi-select-${label}`}
label={`${label}${required ? "*" : ""}`}
/>
Also remove the defaultValue={defaultValue} from the Controller in MultiSelectfield so the Controller looks like this:
<Controller
name={name}
control={control}
render={({ field }) => (
Since you are using controlled components, we need to make sure we set the default value of selectedVal to the default value passed in as a prop:
const [selectedVal, setSelectedVal] = useState<string[]>(defaultValue);
selectedVal is supposed to be a string[] not a string, so we should change the type of defaultValue to a string[]:
interface IFormElementTypes {
name: string;
label: string;
// eslint-disable-next-line react/require-default-props
required?: boolean;
defaultValue: string[];
options: IOptionTypes[];
placeholder: string;
}
Finally, back in App.tsx we should pass an array to MultiSelectField as the defaultValue prop. Here's what it would look like if we wanted "John" to be selected by default:
<MultiSelectField
name={`items.${index}.name`}
label="Choose Name"
placeholder="Name"
defaultValue={["john"]}
options={names}
/>
Or, if we wanted to have the default be "John" and "Smith" then we could set the default like this:
defaultValue={["john", "smith"]}
Here's a full working example on CodeSandbox:
i'm quite new with react and i'm building a form with react-hook and useState to manage my datas after the submit.
I'm not able to use textfield as they are blocked. I think that i make some errors into value/onChange parameters but i don't know what type of error.
import React, { useState } from "react";
import {
TextField,
MenuItem,
Typography,
Checkbox,
Divider,
Button,
} from "#mui/material";
import { MdError } from "react-icons/md";
import { BsArrowRight } from "react-icons/bs";
import "../style/contactform.scss";
import { useForm } from "react-hook-form";
const initialState = {
name: "",
email: "",
};
const ContactForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const [state, setState] = useState(initialState);
const { name, email } = state;
const handleInputChange = (e) => {
const { name, value } = e.target;
setState({ ...state, [name]: value });
};
const onSubmit = (e) => {
e.preventDefault();
console.log("form submit");
setState(initialState);
};
return (
<form className="contact-form" onSubmit={handleSubmit(onSubmit)}>
<Typography variant="h4" className="form-title">
Be the first.
</Typography>
<div className="form-component">
<TextField
id="standard-basic"
label="Nome*"
variant="standard"
name="nome"
value={name}
onChange={handleInputChange}
{...register("nome", {
required: true,
})}
/>
{errors?.nome?.type === "required" && (
<MdError className="form-validation-icon" />
)}
</div>
<Divider className="form-hr" />
<div className="form-component">
<TextField
id="standard-basic"
label="Email*"
variant="standard"
name="email"
value={email}
onChange={handleInputChange}
{...register("email", {
required: true,
pattern: {
value:
/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
},
})}
/>
{errors?.email?.type === "required" && (
<MdError className="form-validation-icon" />
)}
{errors?.email?.type === "pattern" && (
<Typography variant="p" className="form-validation-email">
Inserisci un indirizzo email valido.
</Typography>
)}
</div>
<Divider className="form-hr" />
<Button className="form-submit" type="submit" variant="contained">
<BsArrowRight />
</Button>
</form>
);
};
export default ContactForm;
Textfields are completely block but initial state is actually working, do i miss something?
Can you help me?
To assign initial values using the useForm hook, you pass it under the defaultValues parameter passed to the hook like so:
const {
register,
handleSubmit,
reset
formState: { errors },
} = useForm({
defaultValues: initialState
});
Then just pass the ...register name and email to the inputs. There is no need to assign values to them again:
<TextField
id="standard-basic"
label="Name*"
variant="standard"
name="name"
{...register("name", {
required: true,
})}
/>
// for the email..
<TextField
id="standard-basic"
label="Email*"
variant="standard"
name="email"
{...register("email", {
required: true,
pattern: {
value: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+#[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,},
})}
/>
If you'll notice, the values are off the text fields already and there's also no need for the handleInputChange function. The useForm hook takes care of that.
Edit:
In addition to the onSubmit function, the handleSubmit from useForm passes a data object into the function like this:
const onSubmit = (data) => {
console.log("form submitted", data);
reset(); // this can be destructured of the `useForm` hook.
};
For more info check their documentation
I'm trying to figure out how to follow the instructions in the documentation for the Autocomplete field of the Formik, Material UI, React tool here.
The example given in the documentation is:
import { Autocomplete } from 'formik-material-ui-lab';
const options = [{ title: 'The Shawshank Redemption', year: 1994 }, ...]
<Field
name="name"
component={Autocomplete}
options={options}
getOptionLabel={(option: Movie) => option.title}
style={{ width: 300 }}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField
{...params}
error={touched['name'] && !!errors['name']}
helperText={errors['name']}
label="Autocomplete"
variant="outlined"
/>
)}
/>;
No clues are given as to the meaning of Movie where it is used in getOptionLabel. When I try to use this, Movie is underlined as is AutocompleteRenderInputParams in the renderInput object. I don't know why.
I have seen this post which tries an alternative approach, but I can't get that to work either.
I have a form, with two Autocomplete fields. Currently, it looks like this.
When I try to use the form, the submit button hangs and the console log says:
Material-UI: The getOptionLabel method of Autocomplete returned
undefined instead of a string for "".
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import firebase, {firestore} from '../../../firebase';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Button from '#material-ui/core/Button';
import Box from '#material-ui/core/Box';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import {
Formik, Form, Field, ErrorMessage,
} from 'formik';
import * as Yup from 'yup';
import { Autocomplete, ToggleButtonGroup } from 'formik-material-ui-lab';
import { Switch } from 'formik-material-ui';
const styles = {
};
const allCategories = [
{value: 'culture', label: 'Culture'},
{value: 'other', label: 'Other'},
];
const sharingOptions = [
{value: 'open', label: 'Openly'},
{value: 'me', label: 'Only me'},
];
function Contact(props) {
const { classes } = props;
const [open, setOpen] = useState(false);
const [isSubmitionCompleted, setSubmitionCompleted] = useState(false);
function handleClose() {
setOpen(false);
}
function handleClickOpen() {
setSubmitionCompleted(false);
setOpen(true);
}
return (
<React.Fragment>
<Button
// component="button"
color="primary"
onClick={handleClickOpen}
style={{ float: "right"}}
variant="outlined"
>
Create an Impact Metric
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
{!isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Create an Impact Metric</DialogTitle>
<DialogContent>
<DialogContentText>
test form.
</DialogContentText>
<Formik
initialValues={{ title: "", category: "", sharing: "" }}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
firestore.collection("testing").doc().set({
values,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.then(() => {
setSubmitionCompleted(true);
});
}}
validationSchema={Yup.object().shape({
title: Yup.string()
.required('Required'),
category: Yup.string()
.required('Required'),
sharing: Yup.string()
.required('Required')
})}
>
{(props) => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<form onSubmit={handleSubmit}>
<TextField
label="Title"
name="title"
className={classes.textField}
value={values.title}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.title && touched.title) && errors.title}
margin="normal"
style={{ width: "100%"}}
/>
<Box margin={1}>
<Field
name="category"
component={Autocomplete}
options={allCategories}
getOptionLabel={option => option.label}
style={{ width: 300 }}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField
{...params}
error={touched['category'] && !!errors['category']}
helperText={
touched['category'] && errors['category']
}
label="Select Category"
variant="outlined"
/>
)}
/>
</Box>
<Box margin={1}>
<Field
name="sharing"
component={Autocomplete}
options={sharingOptions}
getOptionLabel={option => option.label}
style={{ width: 300 }}
renderInput={(params: AutocompleteRenderInputParams) => (
<TextField
{...params}
error={touched['sharing'] && !!errors['sharing']}
helperText={
touched['sharing'] && errors['sharing']
}
label="Select Sharing Option"
variant="outlined"
/>
)}
/>
</Box>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</Button>
<Button type="submit" disabled={isSubmitting}>
Submit
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</form>
);
}}
</Formik>
</DialogContent>
</React.Fragment>
}
{isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Thanks!</DialogTitle>
<DialogContent>
<DialogContentText>
test
</DialogContentText>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleClose}
>
Close
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</DialogContent>
</React.Fragment>}
</Dialog>
</React.Fragment>
);
}
export default withStyles(styles)(Contact);
Can anyone see how to get the autocomplete working with formik, material ui in line with the documentation published at the link above?
I also tried using the regular select form input. This is the form field:
<Box margin={1}>
<Field
component={TextField}
type="text"
name="category"
label="Category"
select
variant="outlined"
helperText="Select a category"
margin="normal"
style={{ width: "100%"}}
InputLabelProps={{
shrink: true,
}}
>
{allCategories.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Field>
When I try this, I get a warning in the console that says:
instrument.ts:129 Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''
This warning doesn't make any sense - the form renders with a menu correctly populated.
I also get an error that says:
index.js:1 Warning: A component is changing an uncontrolled input of
type undefined to be controlled. Input elements should not switch from
uncontrolled to controlled (or vice versa). Decide between using a
controlled or uncontrolled input element for the lifetime of the
component. More info
In relation to that error, I have seen this post, which recommends using value (rather than input - which I do) and defining all the initial values as a type. For me, they are all strings, although I tried replacing the select fields with empty arrays. In both alternatives, the same error message is returned in the console.
At this point - I don't care which of autocomplete or select I use, I just want to get one of them working.
It is interesting that in both cases (using select and autocomplete) the console logs warnings that say:
Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are `one`, `two`.
(anonymous) # 0.chunk.js:141301
0.chunk.js:141301 Material-UI: You have provided an out-of-range value `undefined` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are `a`, `b`, `c`, `d`.
BUT, only one instance of the error that says:
A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: react-website -controlled-components
in input (created by ForwardRef(SelectInput))
in ForwardRef(SelectInput) (created by ForwardRef(InputBase))
in div (created by ForwardRef(InputBase))
in ForwardRef(InputBase) (created by WithStyles(ForwardRef(InputBase)))
in Wi
This error points to the category select form input.
I also tried adding the gender select form field from this code sandbox to my form to see if I could get this working. When I comment out the category and sharing fields described above, and add a gender field with a default value of an empty string, the form loads.
The field is:
<Field
name="gender"
label="Gender"
options={[
{ value: "Male", label: "Male" },
{ value: "Female", label: "Female" },
{ value: "Other", label: "Other" }
]}
component={Select}
/>
The select field for gender appears but is about 1cm wide and the options menu does not populate with options, I can't select anything. BUT the form does load to firebase with an empty string in the gender field. That's progress but not enough to move forward.
The same code sandbox shows a field that uses Autocomplete. I tried to adapt it and use it in my form as follows:
<Field
name="gender"
label="Gender"
options={sharingOptions}
component={Autocomplete}
textFieldProps={{
label: sharingOptions.label
}}
/>
When I try that, I get an error that says:
TypeError: renderInput is not a function
This error message makes no sense to me because I'm not using renderInput anywhere in the form.
When I try:
<Box margin={1}>
<Field
component={Select}
type="text"
name="category"
label="Impact Category"
select
variant="outlined"
helperText="Select a category"
margin="normal"
style={{ width: "100%"}}
InputLabelProps={{
shrink: true,
}}
>
{allCategories.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Field>
</Box>
I get no errors and can save the form with the option details. However, this does not actually solve the problem about why Autocomplete will not work. This is also not using the Select field as shown in the linked documentation. So I'm no clearer on why this works or why the method shown in the documentation does not work.
NEXT ATTEMPT
Using the autocomplete example in this codesandbox as a guide, I tried:
<Field
name="autocomplete"
multiple
component={Autocomplete}
options={sharingOptions}
getOptionLabel={(option: any) => option.title}
style={{width: 300}}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
error={touched['autocomplete'] && !!errors['autocomplete']}
helperText={touched['autocomplete'] && errors['autocomplete']}
label="Autocomplete"
variant="outlined"
/>
)}
/>
As with the earlier example, my code editor underlines the value "any" where it appears in getOptionLabel and it also underlines AutocompleteRenderInputParams. I can't find any documentation explaining what these elements of the form field mean or do. In any event, I have imported AutocompleteRenderInputParams as shown in the code sandbox.
I made the initial value of the autocomplete field in my form an empty array - although I note the code sandbox does not set an initial value in this example. When I try removing the initial value of autocomplete, I get the same errors as are generated when the initial value is an empty array, but I also get a warning in the console that says:
Warning: value for autocomplete is not an array, this can caused
unexpected behaviour
When I try this code, my console logs the following errors:
TypeError: Cannot read property 'toLowerCase' of undefined
Material-UI: The getOptionLabel method of Autocomplete returned
undefined instead of a string for {"value":"open","label":"Open
"}.
Working example:
Demo
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import { Formik, Field } from "formik";
import { Autocomplete } from "formik-material-ui-lab";
import { TextField } from "#material-ui/core";
const options = [
{ title: "The Shawshank Redemption", year: 1994 },
{ title: "Inglourious Basterds", year: 2009 },
{ title: "Snatch", year: 2000 },
{ title: "3 Idiots", year: 2009 },
{ title: "Monty Python and the Holy Grail", year: 1975 }
];
function App() {
return (
<Formik
initialValues={{
autocomplete: null
}}
>
{() => (
<Field
name="autocomplete"
component={Autocomplete}
options={options}
getOptionLabel={(option) => option.title}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Autocomplete" variant="outlined" />
)}
/>
)}
</Formik>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
For others who are stuck, this works - although I don't understand why. The underlined errors noted in the post above remain and I don't know how to solve for them.
Sharing this as a way forward - rather than a good solution.
import React, { useState } from 'react';
import {render} from 'react-dom';
import { Link } from 'react-router-dom';
import firebase, {firestore} from '../../../../firebase';
import { withStyles } from '#material-ui/core/styles';
import {
Button,
LinearProgress,
MenuItem,
FormControl,
InputLabel,
FormControlLabel,
TextField,
Typography,
Box,
Grid,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from '#material-ui/core';
import MuiTextField from '#material-ui/core/TextField';
import ToggleButton from '#material-ui/lab/ToggleButton';
import FormatAlignLeftIcon from '#material-ui/icons/FormatAlignLeft';
import FormatAlignCenterIcon from '#material-ui/icons/FormatAlignCenter';
import FormatAlignRightIcon from '#material-ui/icons/FormatAlignRight';
import FormatAlignJustifyIcon from '#material-ui/icons/FormatAlignJustify';
import {
Formik, Form, Field, ErrorMessage,
} from 'formik';
import * as Yup from 'yup';
// import { Autocomplete, ToggleButtonGroup } from 'formik-material-ui-lab';
import {
Autocomplete,
ToggleButtonGroup,
AutocompleteRenderInputParams,
} from 'formik-material-ui-lab';
import {
fieldToTextField,
TextFieldProps,
Select,
Switch,
} from 'formik-material-ui';
const allCategories = [
{value: 'one', label: 'Col'},
{value: 'two', label: 'Com'},
];
function UpperCasingTextField(props: TextFieldProps) {
const {
form: {setFieldValue},
field: {name},
} = props;
const onChange = React.useCallback(
event => {
const {value} = event.target;
setFieldValue(name, value ? value.toUpperCase() : '');
},
[setFieldValue, name]
);
return <MuiTextField {...fieldToTextField(props)} onChange={onChange} />;
}
function Glossary(props) {
const { classes } = props;
const [open, setOpen] = useState(false);
const [isSubmitionCompleted, setSubmitionCompleted] = useState(false);
function handleClose() {
setOpen(false);
}
function handleClickOpen() {
setSubmitionCompleted(false);
setOpen(true);
}
return (
<React.Fragment>
<Button
// component="button"
color="primary"
onClick={handleClickOpen}
style={{ float: "right"}}
variant="outlined"
>
Create a Defined Term
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
{!isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Create </DialogTitle>
<DialogContent>
<DialogContentText>
</DialogContentText>
<Formik
initialValues={{ term: "", definition: "", category: [], attribution: true, attributionRegion: '', context: "", relatedTerms: "", linkedTemplates: "", referenceMaterials: "" }}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
firestore.collection("glossary").doc().set({
...values,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.then(() => {
setSubmitionCompleted(true);
});
}}
validationSchema={Yup.object().shape({
term: Yup.string()
.required('Required'),
definition: Yup.string()
.required('Required'),
category: Yup.string()
.required('Required'),
attribution: Yup.boolean()
.required('Required'),
context: Yup.string()
.required("Required"),
})}
>
{(props) => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<form onSubmit={handleSubmit}>
<TextField
label="Term"
name="term"
className={classes.textField}
value={values.term}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.term && touched.term) && errors.term}
margin="normal"
style={{ width: "100%"}}
/>
<TextField
label="Meaning"
name="definition"
multiline
rows={4}
className={classes.textField}
value={values.definition}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.definition && touched.definition) && errors.definition}
margin="normal"
style={{ width: "100%"}}
/>
<TextField
label="How is it used?"
name="context"
className={classes.textField}
multiline
rows={4}
value={values.context}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.context && touched.context) && errors.context}
margin="normal"
style={{ width: "100%"}}
/>
<Box margin={1}>
<Typography component="div" style={{ marginTop: "5vh", marginBottom: "5vh"}}>
Choose)?
<Grid component="label" container alignItems="center" spacing={1}>
<Grid item>Attribution</Grid>
<Grid item>
<Field
component={Switch}
name="attribution"
type="checkbox"
>
</Field>
</Grid>
<Grid item>Anonymous</Grid>
</Grid>
</Typography>
</Box>
<Box margin={1}>
<Field
name="category"
multiple
component={Autocomplete}
options={allCategories}
getOptionLabel={(option: any) => option.label}
style={{width: 300}}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
error={touched['autocomplete'] && !!errors['autocomplete']}
helperText={touched['autocomplete'] && errors['autocomplete']}
label="Category"
variant="outlined"
/>
)}
/>
</Box>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</Button>
<Button type="submit" disabled={isSubmitting}>
Submit
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</form>
);
}}
</Formik>
</DialogContent>
</React.Fragment>
}
{isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Thanks!</DialogTitle>
<DialogContent>
<DialogContentText>
Thank you </DialogContentText>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleClose}
>
Close
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</DialogContent>
</React.Fragment>}
</Dialog>
</React.Fragment>
);
}
export default withStyles(styles)(Glossary);
I working on a react project where I have requirement like,
I have array inside contain, 1 Object and 1 Array named Task[]
"contractor": [
{
"contractGivenBy": -1,
"contractorID": 0,
"contractorName": "contractor1",
"reviewedByAssigner": false,
"submitReviewToAssigner": false,
"tasks": [ 2, 4, 6 ],
"tasksDone": false
},
Now, I want to display the Tasks array as Checkboxes in the page.
That is nice, I displayed all checkboxes using map() method, But the problem is, How to handle (get values from those checkboxes) when user checked or unchecked the specific checkbox.
I'm using React functional component with React hooks.
Here is what is tried..
<form onSubmit={onSubmitHandler}>
{
projectData.contractor[0].tasks.map((task, index) => {
return (
<div style={{ flexDirection: "column" }}>
<FormControlLabel
control={
<Checkbox
checked={false}
value={task}
onChange={handleTask} />
}
label={`task ${task}`}
/>
</div>
)
})
}
<Button
type="submit"
style={{
backgroundColor:"rgba(25,123,189)",
color: "white"
}}>
Assgin
</Button>
</form>
UPDATED
Here you go , it uses react hooks with checkbox implementation, i have tweaked it a little with <input type /> but you will get the idea
import React, { useState } from "react";
import ReactDOM from "react-dom";
const Checkbox = ({ type = "checkbox", name, checked = false, onChange }) => {
console.log("Checkbox: ", name, checked);
return (
<input type={type} name={name} checked={checked} onChange={onChange} />
);
};
const CheckboxExample = () => {
const [checkedItems, setCheckedItems] = useState({});
const handleChange = event => {
setCheckedItems({
...checkedItems,
[event.target.name]: event.target.checked
});
console.log("checkedItems: ", checkedItems);
};
const checkboxes = [
{
name: "check-box-1",
key: "checkBox1",
label: "Check Box 1"
},
{
name: "check-box-2",
key: "checkBox2",
label: "Check Box 2"
}
];
return (
<div>
<lable>Checked item name : {checkedItems["check-box-1"]} </lable> <br />
{checkboxes.map(item => (
<label key={item.key}>
{item.name}
<Checkbox
name={item.name}
checked={checkedItems[item.name]}
onChange={handleChange}
/>
</label>
))}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<CheckboxExample />, rootElement);