Formik and Material-UI - javascript

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.

Related

Custom input from react-form-hook not working

Following the example here I have a custom input component:
Input.tsx
import React from "react";
export default function Input({label, name, onChange, onBlur, ref}:any) {
return (
<>
<label htmlFor={name}>{label}</label>
<input
name={name}
placeholder="Jane"
onChange={onChange}
onBlur={onBlur}
ref={ref}
/>
</>
);
}
An example of the usage:
import {useAuth} from '../components/AuthContextProvider'
import { useForm, SubmitHandler } from "react-hook-form";
import Input from '../components/Input'
function Subscriptions({ Component, pageProps }: any) {
const { user } = useAuth()
const { register, handleSubmit, formState: { errors } } = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = async data => {
console.log(data, 'data sdfdsg')
}
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<Input label="name" {...register('name')}/>
<button type='submit'>Add kid</button>
</form>
</>
)
}
export default Subscriptions
Here's my package version:
"react-hook-form": "^7.34.2"
Any ideas what I'm doing wrong here?
The custom input receives undefined but it works with normal <input /> tags.
I've used the example in a codesandbox and it threw errors about ref and it suggested using React.forwardRef, change your custom Input to this:
function Input({ label, name, onChange, onBlur }, ref) {
return (
<>
<label htmlFor={name}>{label}</label>
<input
name={name}
placeholder="Jane"
onChange={onChange}
onBlur={onBlur}
ref={ref}
/>
</>
);
}
const MyInput = React.forwardRef(Input);
export default MyInput;
and by the way, ref is not part of props I don't know why the example has it like that, it's necessary to use forwardRef so it is passed as second argument.
you can see the full example in this codesandbox

How to set input direction in react-hook-form?

I'm building a website that uses "RTL (Right To Left) language", so I implemented react-hook-form, but react hook form uses only "LTR" forms like so...
In here I'm using the HTML attribute (dir="RTL") in my page, so all the text is "RTL" except the react hook form how can I fix this?.
import { TextField, Grid } from "#material-ui/core";
import { useFormContext, Controller } from "react-hook-form";
const FormInput = ({ name, label }) => {
const { control } = useFormContext();
return (
<Grid item xs={12} sm={6}>
<Controller
as={TextField} control={control} fullWidth name={name} label={label} required defaultValue=""/>
</Grid>
)
}
export default FormInput;
this is my FormInput component, I export this component to my address form component
import { useForm, FormProvider } from "react-hook-form";
<FormProvider>
<form>
<FormInput name="firstName" label="الإسم الأول (first name)" />
<FormInput name="lastName" label="اسم العائلة (last name)" />
</form>
</FormProvider>
this is my address form component.

Delete tag in uncontrolled "ChipInput" using react hook form

I'm using react-hook-form to handle form values, Its working fine for all other input types like TextFeild, Select from material but facing issues with "material-ui-chip-input" as adding tag working fine but not able to delete tag on click of cross button or hitting backspace. I'm struggling on this from a long. Anyone please help in it.
import React from "react";
import FormControl from "#material-ui/core/FormControl";
import { Controller } from "react-hook-form";
import ChipInput from "material-ui-chip-input";
const ReactHookFormChips = ({
name,
label,
control,
defaultValue,
children,
rules,
error,
chipsError,
...props
}) => {
const labelId = `${name}-label`;
return (
<FormControl {...props}>
<Controller
as={
<ChipInput
label={label}
helperText={chipsError}
error={error}
/>
}
name={name}
control={control}
defaultValue={defaultValue}
rules={rules}
/>
</FormControl>
);
};
export default ReactHookFormChips;
calling this component like
<ReactHookFormChips
id="levelLabel"
name="tags"
label="Select Tags"
control={control}
defaultValue={[]}
margin="normal"
error={!!errors?.tags}
rules={{ required: true }}
chipsError={errors?.tags && "Tag is required."}
/>
I fixed it using render prop.
import React from "react";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import { Controller } from "react-hook-form";
import ChipInput from "material-ui-chip-input";
const ReactHookFormChips = ({
name,
label,
control,
defaultValue,
children,
rules,
error,
chipsError,
...props
}) => {
const labelId = `${name}-label`;
return (
<FormControl {...props}>
<Controller
render={({ onChange, onBlur, value }) =>
<ChipInput
onChange={onChange}
label={label}
helperText={chipsError}
error={error}
/>
}
name={name}
control={control}
defaultValue={defaultValue}
rules={rules}
/>
</FormControl>
);
};
export default ReactHookFormChips;

How to pass Material UI data to Formik?

I am trying to add a Material UI 'Select' to my Formik component, but can't pass the values to Formik's initialValues.
const [hours, setHours] = React.useState('');
const handleHourChange = ({ target }) => {
setHours(target.value);
};
<Formik
initialValues={{
price: '' //not Material UI. Works.
hours: hours //from Material UI
}}
<Form>
<label htmlFor={'price'}> Price </label>
<Field
name={'price'}
type="text" //this Field (not Material UI) works fine.
/>
//...
//the below, which is Material UI's
//doesn't send its values to Formik's initialValues
<FormControl className={classes.formControl}>
<InputLabel id="demo-simple-select-label">Hours</InputLabel>
<Select
name={'hours'} //I added this name prop but not sure it's making any difference
labelId="demo-simple-select-label"
id="demo-simple-select"
value={hours}
onChange={handleHourChange}>
<MenuItem value={60}>01</MenuItem>
<MenuItem value={120}>02</MenuItem>
</Select>
</FormControl>
</Form>
</Formik>
What could be done to fix this? I don't seem to know how to properly get Material UI's values to add them to Formik.
For material ui, you can use this great library
https://github.com/stackworx/formik-material-ui
Here is their sample codesandbox
https://codesandbox.io/s/915qlr56rp?file=/src/index.tsx
If you don't want to use their library, you can use Formik's component prop to use your custom component
Based on your comment request, I've written the code here at codesandbox
https://codesandbox.io/s/react-formik-material-ui-select-huzv7?file=/src/App.js
I'm not sure why you have state variables for formik values. Those are handled by formik. We don't need to handle them manually.
import React from "react";
import { Formik, Form, Field } from "formik";
import {
Select,
InputLabel,
MenuItem,
FormControl,
Button
} from "#material-ui/core";
import "./styles.css";
const CustomizedSelectForFormik = ({ children, form, field }) => {
const { name, value } = field;
const { setFieldValue } = form;
return (
<Select
name={name}
value={value}
onChange={e => {
setFieldValue(name, e.target.value);
}}
>
{children}
</Select>
);
};
export default function App() {
/*
You don't need to handle the formik values as state.
Formik handles it itself
const [hours, setHours] = React.useState("");
const handleHourChange = ({ target }) => {
setHours(target.value);
};
*/
return (
<Formik
initialValues={{
price: "abcv", //not Material UI. Works.
hours: 60 //from Material UI
}}
onSubmit={(values, actions) => {
alert("values:" + JSON.stringify(values));
}}
>
<Form>
<label htmlFor={"price"}> Price </label>
<Field
name={"price"}
type="text" //this Field (not Material UI) works fine.
/>
<FormControl>
<InputLabel id="demo-simple-select-label">Hours</InputLabel>
<Field name="hours" component={CustomizedSelectForFormik}>
<MenuItem value={60}>01</MenuItem>
<MenuItem value={120}>02</MenuItem>
</Field>
</FormControl>
<Button type="submit">Submit</Button>
</Form>
</Formik>
);
}

Redux Form + React Native Elements firing validation too soon

I'm using redux-form in React Native, along with React Native Elements to create forms.
The problem is, after the screen is rendered, the validations are getting called before the user has even had a chance to touch the input fields, showing input validations error too early.
My code:
FormInput:
import React from 'react'
import { Input } from "react-native-elements";
export default FormInput = (props) => {
const { input, meta: { error }, ...inputProps } = props
return <Input {...inputProps}
onChangeText={input.onChange}
onBlur={input.onBlur}
onFocues={input.onFocus}
value={input.value}
errorMessage={error}
/>
}
LoginForm:
import React from 'react';
import { reduxForm, Field, isDirty } from 'redux-form/immutable';
import { View, Image } from 'react-native';
import { Button } from "react-native-elements";
import { required, email } from 'redux-form-validators'
import FormInput from "./FormInput";
import styles from "./Styles/LoginFormStyles";
import Colors from '../Themes/Colors'
const bla = (value) => {
console.tron.log('value = ' + value)
return value !== undefined ? undefined : 'is required'
}
const LoginForm = (props) => {
const { handleSubmit, submitting } = props
return (
<View style={styles.container}>
<Image source={require('../Images/logo.png')} style={styles.logoContainer} resizeMode='center' />
<View style={styles.buttonHolderContainer}>
<Field name="email" validate={bla} textContentType="emailAddress" keyboardType="email-address" autoCapitalize = 'none' component={FormInput} placeholder="Email" containerStyle={styles.inputContainerHolder} />
<Field name="password" validate={[required()]} secureTextEntry component={FormInput} placeholder="Senha" containerStyle={styles.inputContainerHolder} />
<Button title="Entrar" style={{ marginTop: 10 }} buttonStyle={{ backgroundColor: Colors.appColor }} titleStyle={{ color: 'black' }}
onPress={handleSubmit} loading={submitting} disabled={submitting}/>
</View>
</View>
)
}
export default reduxForm({ form: 'LoginForm'})(LoginForm)
Versions:
"react-native": "0.57.7",
"react-native-elements": "^1.0.0",
"react-navigation": "3.1.3",
"react-redux": "^5.0.6",
"redux": "^4.0.0",
"redux-form": "^8.1.0"
The Redux call chain:
As you can see, the UPDATE_SYNC_ERRORS event is getting dispatched before any user interactions to the input.
Thanks for your time!
I think you may want to look at using the helper function shouldError() so you can control when redux-form chooses to perform its validation.
Although the code example is out-of-date because it uses the deprecated shouldValidate() you can still get an idea of how to use shouldError() by taking a look at this SO answer.
Inside of meta you have properties like touched, visited etc. You can use those property to show error based on condition:
meta.touched: true if the field has been touched. By default this will be set when the field is blurred.
meta.visited: true if this field has ever had focus. It will only work if you are passing onFocus to your input element.
So the errorField can be:
errorMessage={meta.touched && error}

Categories