The form input campaignid is automatically filled in when the page is loaded.
I've added onChange and onBlur to name input to prevent default, however when you click on the submit button, no action is taken.
I'm using a third party script to get URL parameters and insert them into campaignid value.
import * as React from "react";
import Script from "next/script";
import { Field, Form, Formik } from "formik";
import {
Button,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
Input,
} from "#chakra-ui/react";
export default function FormikForm() {
function validateName(value) {
let error;
if (!value) {
error = "Type a valid name.";
}
return error;
}
return (
<>
<Script src="https://cdn.jsdelivr.net/gh/gkogan/sup-save-url-parameters/sup.min.js" />
<Formik
initialValues={{
name: "",
}}
onSubmit={(values) => {
setTimeout(() => {
fetch(`https://hooks.zapier.com/hooks/catch/3660927/bte5w75/`, {
method: "POST",
body: JSON.stringify(values, null, 2),
}),
3000;
});
}}
>
{(props) => (
<Flex>
<Form id="hero">
<Field name="name" validate={validateName}>
{({ field, form }) => (
<FormControl
isInvalid={form.errors.name && form.touched.name}
>
<FormLabel htmlFor="name">Nome</FormLabel>
<Input
{...field}
id="name"
onChange={(e) => {
e.preventDefault();
}}
onBlur={(e) => {
e.preventDefault();
}}
/>
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="campaignid">
{({ field }) => (
<FormControl isReadOnly>
<Input {...field} type="hidden" id="campaignid" />
</FormControl>
)}
</Field>
<Button
id="submited"
isLoading={props.isSubmitting}
type="submit"
>
Submit
</Button>
</Form>
</Flex>
)}
</Formik>
</>
);
}
Related
I build a simple form using formik. I have a situation that one field is hidden after I choose in radio buttun 'no'.
But if I enter value to this field and then choose 'no' to hide it, and submit the form I will get in the final json also the value of the hidden field.
My expectation is when field isn't show to the screen, remove it from the final json.
My form:
import React from "react";
import { Formik } from "formik";
import { Form } from "react-bootstrap";
import { Radio } from "antd";
const CustomFormik = () => {
return (
<>
<Formik
initialValues={{
input1: null,
radio: null,
input2: null
}}
onSubmit={(values) => {
console.log(values);
}}
>
{(formik) => (
<>
<Form
onSubmit={formik.handleSubmit}
className="custom-formik-container"
>
<Form.Group>
<span>input1: </span>
<Form.Control
id={"input1"}
name={"input1"}
{...formik.getFieldProps("input1")}
/>
</Form.Group>
{formik.getFieldProps("radio").value !== 2 && (
<Form.Group>
<span>input2: </span>
<Form.Control
id={"input2"}
name={"input2"}
{...formik.getFieldProps("input2")}
/>
</Form.Group>
)}
<Form.Group>
<span>radio: </span>
<Radio.Group {...formik.getFieldProps("radio")}>
<Radio value={1}>yes</Radio>
<Radio value={2}>no</Radio>
</Radio.Group>
</Form.Group>
</Form>
<button
style={{
width: 200,
height: 30,
background: "#113776",
color: "#fff"
}}
onClick={() => formik.handleSubmit()}
>
S U B M I T
</button>
</>
)}
</Formik>
</>
);
};
export default CustomFormik;
For example enter:
'abc' to the first input.
'efg' to the second input.
'no' to the radio button.
The final json in console.log will be:
{input1: "abc",input2: "efg", radio: 2}
But my expectation in to be:
{input1: "abc", radio: 2}
codesandbox
Thank you guys!
I don't know if this fits your needs but I had a similar case in my project using redux-form and what i did was to delete the property before submiting it
sth like this
onSubmit={(values) => {
if (values.radio === 2) {
let clonedValues = { ...values };
delete clonedValues.input2;
console.log(clonedValues);
} else {
console.log(values);
}
}}
this will do the job.
onSubmit={(values) => {
if(values.radio === 2){
delete values['input2'];
}
console.log(values);
}}
I'm using a custom component in formik field, and need to call a function upon text change inside function (in order to get all matching hints related to input)
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
key="assignees"
className="input-fields"
placeholder="Type names of assignees to start getting suggestions"
onChange={(e) => {
form.setFieldValue(field.name, e.target.value);
getMatching(field.value);
}}
{...props}
{...form}
// {...field}
/>
);
}}
/>
The matching function that's being called in upper code is as below. It updates a state and that state is then being showed inside span in a formik field.
const getMatching = (keyword) => {
for (const item of users) {
if (keyword !== "" && item.name.startsWith(keyword)) {
setMatching(item.name);
return;
} else {
setMatching(undefined);
}
}
};
Expected Behaviour
Formik input doesn't lose focus and retains all the input texts I added upon state change or rerender
Actual Behaviour
Input component loses focus and field is reset whenever a state is set by getMatching function
EDIT- CODE SANDBOX LINK
https://codesandbox.io/s/wizardly-merkle-w191l
A few things:
no need to store matching in state, it can be derived from looking at the assignee textbox value and the list of users
the assignee textbox (I'm calling it searchAssignee) and the list of selected assignees (assignees) are essentially two different inputs, so must be separate Formik inputs
separating the two above inputs fixes the focus issue
The whole idea of formik is that it removes the need to store input values in state. I removed the unnecessary state and separated the above inputs.
Working Example
const getMatching = (users, keyword) => {
if (!keyword) return null;
const match = users.find(({ name }) => name.startsWith(keyword));
if (!match) return null;
return match.name;
};
const FormContainer = ({ users }) => {
return (
<Formik
initialValues={{
summary: "",
description: "",
searchAssignee: "",
assignees: []
}}
onSubmit={async (values) => {
console.log(values);
}}
>
<Form>
<Row>
<Col>
<div className="textcolor">Summary</div>
<Field
id="summary"
name="summary"
placeholder="Add a short summary for your task"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<input
className="input-fields"
{...props}
// {...form}
{...field}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Description</div>
<Field
id="description"
name="description"
placeholder="Description"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
return (
<ReactQuill
theme="snow"
value={field.value}
onChange={field.onChange(field.name)}
style={{ minHeight: "5rem" }}
{...field}
{...props}
/>
);
}}
/>
</Col>
</Row>
<Row>
<Col>
<div className="textcolor">Assignees</div>
<Col className="assignee-wrapper d-flex">
<div className="d-flex">
<Field
id="assignees"
name="assignees"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => (
<>
{field.value.map((item) => (
<div
className="assignee-tag d-flex"
onClick={() => {
const newAssignees = field.value.filter(
(val) => val !== item
);
form.setFieldValue(field.name, newAssignees);
}}
>
<div>{item}</div>
<GrFormClose />
</div>
))}
</>
)}
/>
</div>
<div className="d-flex col">
<Field
id="searchAssignee"
name="searchAssignee"
placeholder="Type names of assignees to start getting suggestions"
component={({
field, // { name, value, onChange, onBlur }
form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
...props
}) => {
const suggestion = getMatching(users, field.value);
return (
<>
<input
className="input-fields"
{...props}
{...form}
{...field}
/>
{suggestion && (
<span
className="text-nowrap"
onClick={() => {
if (!form.values.assignees.includes(suggestion)) {
form.setFieldValue("assignees", [
...form.values.assignees,
suggestion
]);
}
}}
>
{suggestion}
</span>
)}
</>
);
}}
/>
</div>
</Col>
</Col>
</Row>
<div className="mt-2 float-end">
<button className="btn btn-dark btn-sm" type="submit">
Add
</button>
<button className="btn btn-light btn-sm ms-2" type="reset">
Cancel
</button>
</div>
</Form>
</Formik>
);
};
const App = ({ isModalVisible, setModalVisible }) => {
const [users, setUsers] = useState([
{ name: "Hello World" },
{ name: "Foo Bar" }
]);
return (
<Modal show={true}>
<Modal.Body>
<Row>
<div>
<GrFormClose
className="float-end"
onClick={() => setModalVisible(false)}
style={{ cursor: "pointer" }}
/>
</div>
<div>
<FormContainer
users={users}
/>
</div>
</Row>
</Modal.Body>
</Modal>
);
};
I am using react-hook-form V7 with Material UI to create a simple form however, there is a weird behavior when setting the mode in useForm({mode: ".."})
Here is my code snippet:
import { yupResolver } from "#hookform/resolvers/yup";
import { Button, Grid, Link, TextField, Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import Alert from "#material-ui/lab/Alert";
import { Controller, useForm } from "react-hook-form";
import { _MESSAGES } from "src/constants/messages";
import * as yup from "yup";
import AuthContainer from "./AuthContainer";
// ##################### Helpers ######################
/**
* #param {object} error
* #param {string} msg
*/
const renderError = (error, msg) =>
error ? <Alert severity="error">{msg}</Alert> : "";
// ############## Schema & Default Values ##############
const schema = yup.object().shape({
email: yup.string().required(),
password: yup.string().required(),
});
const defaultValues = {
email: "",
password: "",
};
// ###################### Styles ######################
const useStyles = makeStyles((theme) => ({
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1),
},
submit: {
margin: theme.spacing(3, 0, 2),
},
}));
// ################# Main Component ###################
const SignInPage = () => {
const classes = useStyles();
const {
handleSubmit,
formState: { errors },
reset,
control,
} = useForm({
mode: "all",
resolver: yupResolver(schema),
defaultValues,
});
const onSubmit = (data) => {
console.log(data);
// Reset form fields
reset();
};
console.log(errors);
const Form = () => (
<>
<Typography component="h1" variant="h5">
Sign In
</Typography>
<form className={classes.form} onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="email"
render={({ field }) => (
<TextField
variant="outlined"
margin="normal"
fullWidth
autoComplete="email"
label="Email Address"
{...field}
error={errors.email?.message ? true : false}
/>
)}
/>
{renderError(errors.email?.message, _MESSAGES.required)}
<Controller
control={control}
defaultValue=""
name="password"
render={({ field }) => (
<TextField
variant="outlined"
margin="normal"
fullWidth
autoComplete="current-password"
type="password"
label="Password"
{...field}
error={errors.password?.message ? true : false}
/>
)}
/>
{renderError(errors.password?.message, _MESSAGES.required)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
>
Sign In
</Button>
<Grid container>
<Grid item xs={12}>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2" color="secondary">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</>
);
return <AuthContainer Form={<Form />} />;
};
export default SignInPage;
What might have gone wrong?
Here is a Gif of what happened:
selected the input field
started typing
after one char it leaves the input field so, I need to select it to type again.
Move your Form component to its own Component, otherwise, you are mount and unmount your Form Component each re-render.
const App = () => {
const Form = () => {} // mount and unmount with each re-render
return <div><Form /></div>
}
to
const Form = () => {}
const App = () => {
return <div><Form /></div>
}
You have to pass down the ref. For MUI TextField should do the following:
<Controller
control={methods.control}
name="fieldName"
render={({ field: { ref, ...field } }) => (
<TextField {...field} inputRef={ref} /> // ⬅️ The ref
)}
/>
A working demo: https://codesandbox.io/s/broken-microservice-xpuru
Does it work now ?
I want to trigger formik errors and touched whenever the input is clicked and the value is not correct .
I pass formik props to the input component like this :
const initialValues = {
title: ''
};
const validationSchema = yup.object({
title: yup.string().max(50, 'less than 50 words !!').required('required !!')
});
function Add() {
<Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
{(props) => {
return (
<Form>
<AddTitle props={props} />
</Form>
);
}}
</Formik>
);
}
Here I'm trying to display error message whenever the input is touched and there is an error with it like this :
import { Input } from 'antd';
function AddTitle(props) {
console.log(props.props);
return (
<Field name="title">
{() => {
return (
<Input
onChange={(e) => {
props.props.setFieldValue('title', e.target.value)
}}
/>
);
}}
</Field>
<ErrorMessage name="title" />
<P>
{props.props.touched.title && props.props.errors.title && props.props.errors.title}
</P>
</React.Fragment>
);
}
But ErrorMessage and the paragraph below it doesn't work when the input is touched and empty .
In console log it shows that input doesn't handle formik touched method and it only triggers the error for it :
touched:
__proto__: Object
errors:
title: "less than 50 words !"
__proto__: Object
How can I use ErrorMessage properly while passing in formik props to a component and using a third library for inputs ?
Fixed the issue by adding the onBlur to the input and ErrorMessage is working fine :
<Field name="title">
{() => {
return (
<Input
onBlur={() => props.props.setFieldTouched('title')}
onChange={(e) => {
props.props.setFieldValue('title', e.target.value);
}}
/>
);
}}
</Field>
<P class="mt-2 text-danger">
<ErrorMessage name="title" />
</P>
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 });
}