i was trying for 3 days now to get array validation
working with Formik and Yup in React Native with a
FieldArray. The Validation was working but i can't
get it how to display the errors.
I have many tasks from the api where the user needs to write a
comment if the switch is false, for testing purposes
i disabled the switch to find out how to get the array validation
running.
The TaskScreen:
import React, { useState } from 'react';
import { StyleSheet, Button } from 'react-native';
import * as yup from 'yup';
import { Formik } from 'formik';
import { SubmitButton, Tasks } from "../components/forms";
const validationSchema = yup.object().shape({
tasks: yup.array().of(yup.object().shape({comment: yup.string().required('Required'),})),
});
function TasksScreen({ navigation: { navigate }, route}) {
const [tasks, setTasks] = useState(route.params.tasks);
const handleSubmit = async (values) => {
console.log(values);
};
return (
<>
<Formik
initialValues={{ tasks: [] }}
onSubmit={handleSubmit}
validationSchema={validationSchema}
>
<Tasks name="tasks" tasks={tasks} />
<SubmitButton title="Send" />
</Formik>
<Button onPress={() => navigate('TaskLists') } title="Back"/>
</>
)
}
const styles = StyleSheet.create({
container: {},
});
export default TasksScreen;
The Tasks Component
import React from 'react';
import { useFormikContext, FieldArray } from "formik";
import ErrorMessage from "./ErrorMessage";
import { TextInput, Text, View, StyleSheet, Switch } from 'react-native';
function Tasks({ tasks, name }) {
const {
setFieldTouched,
setFieldValue,
errors,
touched,
values,
} = useFormikContext();
return (
<FieldArray name={name}>
<>
{ tasks.map((task, key)=>{
return (
<>
<View key={key}>
<Text>{task.name} {`${name}[${key}].comment]`}</Text>
<TextInput
onBlur={() => setFieldTouched(`${name}[${key}].comment`)}
onChangeText={(text) => setFieldValue(`${name}[${key}].comment`, text)}
value={values[`${name}[${key}].comment`]}
name={`${name}[${key}].comment`}
placeholder="Task comment please"
/>
<ErrorMessage error={`${errors}${name}[${key}].comment`} visible={`${touched}${name}[${key}].comment`} />
</View>
</>
)
})}
</>
</FieldArray>
);
}
const styles = StyleSheet.create({
container: {},
});
export default Tasks;
I also have problems to console.log the Formik Bag, can't figure it out,
it's my first steps with RN, sorry if the question is boring.
Thanks in advance for any kind of help.
As mentioned here in the validation docs you should create a callback as a child of the Formik component which has an object combined with two params which are errors and touched:
the errors are the validation errors of the forms and the touched is map of field names to **whether** the field has been touched.
See this Sample provide by the Formik Docs.
import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const SignupSchema = Yup.object().shape({
firstName: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
lastName: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
email: Yup.string().email('Invalid email').required('Required'),
});
export const ValidationSchemaExample = () => (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
}}
validationSchema={SignupSchema}
onSubmit={values => {
// same shape as initial values
console.log(values);
}}
>
{({ errors, touched }) => (
<Form>
<Field name="firstName" />
{errors.firstName && touched.firstName ? (
<div>{errors.firstName}</div>
) : null}
<Field name="lastName" />
{errors.lastName && touched.lastName ? (
<div>{errors.lastName}</div>
) : null}
<Field name="email" type="email" />
{errors.email && touched.email ? <div>{errors.email}</div> : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
Related
import React, { useState } from "react";
import FileBase64 from "react-file-base64";
import { useDispatch } from "react-redux";
import { makeStyles } from "#material-ui/core/styles";
import { TextField, Select, Input, MenuItem, Button } from "#material-ui/core";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as yup from "yup";
import { updatePost } from "../actions/post";
const useStyles = makeStyles((theme) => ({
textField: {
marginBottom: theme.spacing(2),
},
buttons: {
marginTop: theme.spacing(2),
},
}));
const tags = ["fun", "programming", "health", "science"];
const postSchema = yup.object().shape({
title: yup.string().required(),
subtitle: yup.string().required(),
content: yup.string().min(20).required(),
tag: yup.mixed().oneOf(tags),
});
const EditPostForm = ({ history, post, closeEditMode }) => {
const dispatch = useDispatch();
const [file, setFile] = useState(post?.image);
const { register, handleSubmit, control, errors, reset } = useForm({
resolver: yupResolver(postSchema),
});
const onSubmit = (data) => {
const updatedPost = {
_id: post._id,
...data,
image: file,
};
dispatch(updatePost(post._id, updatedPost));
reset();
setFile(null);
closeEditMode();
};
const classes = useStyles();
return (
<div>
<form noValidate autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
<TextField
id="title"
label="Başlık"
name="title"
variant="outlined"
className={classes.textField}
size="small"
{...register('title')}
error={errors?.title ? true : false}
fullWidth
defaultValue={post?.title}
/>
<TextField
id="subtitle"
label="Alt Başlık"
name="subtitle"
variant="outlined"
className={classes.textField}
size="small"
{...register('subtitle')}
error={errors?.subtitle ? true : false}
fullWidth
defaultValue={post?.subtitle}
/>
<Controller
render={({field}) => (
<Select
{...field}
input={<Input />}
className={classes.textField}
fullWidth
>
{
tags.map((tag, index) => (
<MenuItem {...field} key={index} value={tag}>
{tag}
</MenuItem>
))
}
</Select>
)}
name='tag'
control={control}
error={errors?.tag ? true : false}
defaultValue={tags[0]}
/>
<TextField
id="content"
label="İçerik"
name="content"
multiline
size="small"
{...register('content')}
rows={16}
className={classes.textField}
variant="outlined"
error={errors?.content ? true : false}
fullWidth
defaultValue={post?.content}
/>
<FileBase64 multiple={false} onDone={({ base64 }) => setFile(base64)} />
<div className={classes.buttons}>
<Button color="primary" variant="outlined" onClick={closeEditMode}>
Vazgeç
</Button>{" "}
<Button color="secondary" variant="outlined" type="submit" >
Kaydet
</Button>
</div>
</form>
</div>
);
};
export default EditPostForm;
I have EditPostForm component, component doesn't give any error but when I tried to submit my form onSubmit function is not triggered.
I used react-hook-form to create my form and I used material UI components inside form.
When I Click button which has type submit does not trigger onSubmit function which is called inside of handleSubmit. Why onSubmit is not triggered?
onSubmit isn't triggered because you may have form errors
You can get errors from formState object (const { formState } = useForm(...))
And then use error={formState.errors?.content ? true : false} in your code
https://react-hook-form.com/api/useform/formstate
See an example here
https://codesandbox.io/s/keen-burnell-2yufj?file=/src/App.js
You need to pass onSubmit and onError both.
Like this:
onPress={handleSubmit(onSubmit, onErrors)}
I faced the same error, the problem was that, my register were Boolean and my input was string, and since the value was not required It didn't show errors until I figure out the problem and change register from Boolean to string
I'm using Formik for validating the fields before creating an entity. There is a Select which opens a dropdown and the user must choose one option from there.
I get an error saying that setFieldValue is not defined but where I've researched before I didn't find any definition of this method, it is just used like that so I don't know why is this happening.
This is my code:
import React from 'react';
import { Formik, Form, Field } from 'formik';
import { Button, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
industry: '',
};
}
onChange = (e, { name, value }) => {
this.setState({ [name]: value });
};
handleSubmit = values => {
// ...
};
render() {
const nameOptions = [
{ text: 'Services', value: 'Services' },
{ text: 'Retail', value: 'Retail' },
];
const initialValues = {
industry: '',
};
const requiredErrorMessage = 'This field is required';
const validationSchema = Yup.object({
industry: Yup.string().required(requiredErrorMessage),
});
return (
<div>
<div>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={e => setFieldValue('industry', e.target.value)} // here it is
/>
<div>
{touched.industry && errors.industry
? errors.industry
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
</div>
);
}
}
The error says: 'setFieldValue' is not defined no-undef - it is from ESLint. How can be this solved?
setFieldValue is accessible as one of the props in Formik.
I tried it on CodeSandbox, apparently, the first parameter in the onChange prop returns the event handler while the second one returns the value selected onChange={(e, selected) => setFieldValue("industry", selected.value) }
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values) => this.handleSubmit(values)}
>
{({ errors, touched, setFieldValue }) => (
//define setFieldValue
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
id="industry" // remove onBlur warning
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={(e, selected) =>
setFieldValue("industry", selected.value)
}
/>
<div>
{touched.industry && errors.industry
? errors.industry
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
try calling it using this structure
onChange = {v => formik.setFieldValue('field', v)}
<Formik
htmlFor="amazing"
initialValues={initialValues}
value={{industry:this.state.industry}}
validationSchema={validationSchema}
onSubmit={(values) => this.handleSubmit(values)}
>
{({ errors, touched }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={(e) =>
this.setState({
industry: e.target.value,
})
} // here it is
/>
<div>
{touched.industry && errors.industry ? errors.industry : null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>;
Replace your onChange by onChange={this.onChange('industry', e.target.value)}
and in your constructor add this line this.onChange = this.onChange.bind(this).
your onChange methode will be:
onChange(e, { name, value }){
this.setState({ [name]: value });
}
I'm trying to use my custom TextField in my RegisterForm with Yup but he's dosnt work.
All time I have a message "⚠ Champ obligatoire." after click on Submit I don't understand why but is good with a simple input.
RegisterPage.js
import React, { useRef } from "react";
import { useForm } from "react-hook-form";
import Button from "../../lib/Button";
import TextField from "../../lib/TextField";
import * as yup from "yup";
const SignupSchema = yup.object().shape({
firstName: yup.string().required("⚠ Champ obligatoire."),
});
export default function Home() {
const { register, handleSubmit, errors, watch } = useForm({
validationSchema: SignupSchema,
});
const onSubmit = (data) => console.log(data);
console.log(errors);
return (
<div style={styles.inputForm}>
<p>Inscription</p>
<form style={{ marginTop: "40%" }} onSubmit={handleSubmit(onSubmit)}>
<label style={styles.label} htmlFor="firstName">
Prénom
</label>
<TextField
style={styles.input}
name="firstName"
placeholder="Toto"
type="text"
ref={register}
/>
<br />
{errors.firstName && (
<p style={styles.error}>{errors.firstName.message}</p>
)}
<br />
<Button
style={{ marginTop: 10 }}
type="submit"
onClick={handleSubmit(onSubmit)}>
Termine ton incription
</Button>
</form>
</div>
);
}
My CustomTextField
CustomTextfield.js
import React from "react";
import PropTypes from "prop-types";
import TextField from "#material-ui/core/TextField";
function CustomField({ InputLabelProps = {}, ...props }) {
return (
<TextField
InputLabelProps={{ shrink: true, ...InputLabelProps }}
{...props}
/>
);
}
CustomField.propTypes = {
classes: PropTypes.object.isRequired,
};
export default CustomField;
Thanks in advance!
You need to use inputRef instead of ref on TextField. ref will be applied to the outermost element which will be the div rendered by FormControl; and this won't be any help with the yup integration. inputRef will forward the ref to the input element.
<TextField
style={styles.input}
name="firstName"
placeholder="Toto"
type="text"
inputRef={register}
/>
Related documentation: https://material-ui.com/api/text-field/#props
I am trying to use Formik with Material-UI text field. Like so:
import TextField from '#material-ui/core/TextField';
import {
Field,
FieldProps,
Form,
Formik,
FormikErrors,
FormikProps
} from 'formik';
import React, { Component } from 'react';
interface IMyFormValues {
firstName: string;
}
class CreateAgreementForm extends Component<{}> {
public render() {
return (
<div>
<h1>My Example</h1>
<Formik
initialValues={{ firstName: '' }}
// tslint:disable-next-line:jsx-no-lambda
onSubmit={(values: IMyFormValues) => alert(JSON.stringify(values))}
// tslint:disable-next-line:jsx-no-lambda
validate={(values: IMyFormValues) => {
const errors: FormikErrors<IMyFormValues> = {};
if (!values.firstName) {
errors.firstName = 'Required';
}
return errors;
}}
// tslint:disable-next-line:jsx-no-lambda
render={(formikBag: FormikProps<IMyFormValues>) => (
<Form>
<Field
name="firstName"
render={({ field, form }: FieldProps<IMyFormValues>) => (
<TextField
error={Boolean(
form.errors.firstName && form.touched.firstName
)}
helperText={
form.errors.firstName &&
form.touched.firstName &&
String(form.errors.firstName)
}
/>
)}
/>
</Form>
)}
/>
</div>
);
}
}
export default CreateAgreementForm;
I want Formik to be responsible for validation and Material-UI for looks.
I want to pass errors.firstName to TextField component but the error doesn't display correctly. How can I fix it so it still will be clear to read? I don't want to write my own TextField component.
I don't think you need another library or even create your own wrapper, I think you need to tweek your code a bit.
One problem you have is that you don't pass an onChange function in the Material TextField so the form value of firstName is always null and so you always get the error, even if you have entered a name.
Try adding a name or id on your TextField and an onChange function like so:
<Field
validateOnBlur
validateOnChange
name="firstName"
render={({ field, form }) => (
<TextField
name={"firstName"}
error={
Boolean(form.errors.firstName && form.touched.firstName)
}
onChange={formikBag.handleChange}
onBlur={formikBag.handleBlur}
helperText={
form.errors.firstName &&
form.touched.firstName &&
String(form.errors.firstName)
}
/>
)}
/>
As mentionned in comments, it may actually be a good idea to implement "wrapper" components, like they did in this samples from Formik or ReactFinalForm :
https://github.com/stackworx/formik-material-ui/tree/master/src
https://github.com/final-form/react-final-form#material-ui-10
The idea is the same : implement custom "wrapper" components to wrap Material-UI components and map Formik or ReactFinalForm APIs props.
The advantages of this approach is to centralize in one place the mapping between the two frameworks, so that you do not repeat the mapping each time, and if one of the framework introduces breaking changes you just have to change those custom "wrapper" components.
You can try this: https://github.com/daixianceng/formik-material-fields
Installation:
npm install --save formik-material-fields
Usage:
import React, { Component } from 'react';
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import { FormikTextField } from 'formik-material-fields';
const validationSchema = Yup.object().shape({
username: Yup.string().required(),
});
const initialValues = {
username: '',
};
class MyForm extends Component {
render() {
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={this.props.onSubmit}
>
{({ isValid }) => (
<Form autoComplete="off">
<FormikTextField
name="username"
label="Username"
margin="normal"
fullWidth
/>
</Form>
)}
</Formik>
);
}
}
Check out the formik docs for <Field /> here: https://jaredpalmer.com/formik/docs/api/field
As an example you could use Material's OutlinedInput to style your input:
<Field as={OutlinedInput} />
If you need to pass more props to OutlinedInput, simply add them to Field and it will pass them on to OutlinedInput:
<Field as={OutlinedInput} color="primary" InputProps={{ startAdornment: <InputAdornment position="start"><AccountCircle /></InputAdornment> }} />
You can use setFieldValue method useful for creating custom input change handlers
<Formik
initialValues={{
name: "",
}}
onSubmit={(values: any) => console.log(values)}
>
{({ handleSubmit, setFieldValue }) => (
<Form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
onChange={(event) => setFieldValue("name", event.target.value)}
type="text"
label="Name"
/>
</Form>
)}
</Formik>
To use material-ui and formik, you can use the variant from the official formik documentation:
https://formik.org/docs/examples/with-material-ui
You could also try this library, which does the heavy-lifting for you and implements the wrapper code around Material-UI components (including <TextField />): https://github.com/stackworx/formik-material-ui.
Installation:
yarn add formik-material-ui
In your Formik form component, pass the <TextField /> component as the component prop of the Formik <Field /> component.
import { Formik, Field, Form } from 'formik';
import { TextField } from 'formik-material-ui';
<Field
name="email"
label="Email"
type="email"
component={TextField}
/>
Formik will continue to handle the validation as expected and will render the Material UI component and error message. There are additional details in the docs for other Mui input components and to aid with customization.
So I keep getting a weird error when running my app. I have done some research and none of the solutions seem to be working for my situation.
First things first, here is my code that is throwing issues:
import React, { Component } from "react";
import { View, Text, TextInput, ActivityIndicator } from "react-native";
import SvgUri from "react-native-svg-uri";
import tokenStore from "../../stores/token";
import authApi from "../../api/authApi";
import { toast } from "../../helpers";
import Button from "../../components/Button";
import HeaderButton from "../../components/Button";
import ScrollWrapper from "../../components/ScrollWrapper";
import Container from "../../components/Container";
import Input from "../../components/Input";
import ContentGroup from "../../components/Content/Group";
import Label from "../../components/Label";
import Logo from "../../components/Logo";
import * as apps from "../../themes/apps";
import LoginPageStyles from "./LoginPage.styles";
import catalyst_logo_white from "../../styles/images/catalyst_logo_white.svg";
type Props = {};
export default class LoginPage extends Component<Props> {
state = { loading: false, email: "", password: "" };
setLoading(loading: boolean) {
this.state.loading = true;
}
handleSubmit = async () => {
this.setLoading(true);
try {
const token = await authApi.login(this.state.email, this.state.password);
tokenStore.set(token);
} catch (e) {
toast.error("Invalid email or password");
} finally {
this.setLoading(false);
}
};
handleForgotPassword = () => {};
render() {
const style = LoginPageStyles.get();
return (
<ScrollWrapper
contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
>
<View style={style.container}>
<View style={style.logoContainer}>
<SvgUri width={186} height={24} source={catalyst_logo_white} />
</View>
<View style={style.loginContainer}>
<Container type="bottom">
<Label>Login</Label>
<Input
size="medium"
icon="user"
placeholder="you#domain.com"
onChangeText={value => this.setState({ email: value })}
value={this.state.email}
autoCapitalize="none"
/>
</Container>
<Container type="bottom">
<Label>Password</Label>
<Input
size="medium"
icon="lock"
placeholder="Your Password"
secureTextEntry={true}
onChangeText={value => this.setState({ password: value })}
value={this.state.password}
autoCapitalize="none"
/>
</Container>
<Container type="bottom" alignment="full">
<Button
size="medium"
style={style.submitButton}
onPress={() => this.props.onSelectApp(apps.contentFuel)}
>
Login
</Button>
</Container>
</View>
</View>
<View style={style.forgotPasswordContainer}>
<Button
linkTo="/forgotPassword"
type="basic"
size="medium"
variant="secondary"
>
Forgot Password
</Button>
</View>
</ScrollWrapper>
);
}
}
The troublesome code is the Buttons. I don't know why it is throwing this error:
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: object.
import React from "react";
import { Text } from "react-native";
import { Link } from "react-router-native";
import FontAwesome, { Icons } from "react-native-fontawesome";
import AppContext from "../../../../AppContext";
import ButtonStyles from "./Button.styles";
export default class Button extends React.Component {
render() {
const { size, type, icon, children, onPress, variant, linkTo } = this.props;
return (
<AppContext.Consumer>
{app => {
const style = ButtonStyles.get({ app, size, type, variant });
return (
<Link
to={linkTo}
onPress={onPress}
style={[style.body, this.props.style]}
>
<React.Fragment>
{icon && (
<FontAwesome style={style.icon}>{Icons[icon]}</FontAwesome>
)}
<Text style={style.text}>{children}</Text>
</React.Fragment>
</Link>
);
}}
</AppContext.Consumer>
);
}
}
Some of the solutions I have found is adding brackets to the Button import but that did not work. I know it is the buttons causing problems because when I remove them entirely, the app runs with no issues.
Does anyone have any idea whats wrong?