handleSubmit with Formik using TypeScript - javascript

I have successfully written a Login component in React using plain Javascript. I would now like to convert this component to TypeScript. I have defined some types and also threw in some "any" just to initially compile. Unfortunately, the onClick parameter in my submit button throws the following error:
TS2322: Type '(e?: FormEvent | undefined) => void' is not assignable to type '(event: MouseEvent<HTMLElement, MouseEvent>)
=> void'.
Here is the relevant code:
class Login extends React.Component<LoginProps, LoginState> {
constructor(props) {
super(props);
this.login = this.login.bind(this);
}
async login(values) {
const user = {
email: values.email,
password: values.password,
};
const query = `mutation userLogin(
$user: UserLoginInputs!
) {
userLogin(
user: $user
) {
token
}
}`;
const data: any = await graphQLFetch(query, { user });
if (data && data.userLogin.token) {
const decoded: any = jwt.decode(data.userLogin.token);
const { onUserChange } = this.props;
onUserChange({ loggedIn: true, givenName: decoded.givenName });
const { history } = this.props;
history.push('/');
}
}
render() {
return (
<Formik
onSubmit={this.login}
validationSchema={loginSchema}
initialValues={{
email: '',
password: '',
}}
>
{({
handleSubmit,
handleChange,
values,
}) => (
<Card>
<Card.Body>
<Form>
<Form.Group>
<Form.Label>E-mail</Form.Label>
<Form.Control
name="email"
value={values.email}
onChange={handleChange}
/>
</Form.Group>
<Form.Group>
<Form.Label>Password</Form.Label>
<Form.Control
name="password"
value={values.password}
onChange={handleChange}
/>
</Form.Group>
</Form>
<Button
type="button"
variant="primary"
onClick={handleSubmit}
>
Submit
</Button>
</Card.Body>
</Card>
)}
</Formik>
);
}
}
I'm new to TypeScript and don't fully understand why an error occurs regarding e versus event when the login function does not explicitly reference either of those. Can someone please help me assign types to my handleSubmit function (aka login)?

I hope that example could help you to resolve your issue
import { Field, Form, Formik } from 'formik';
import * as React from 'react';
import './style.css';
interface MyFormValues {
firstName: string;
}
export default function App() {
const initialValues: MyFormValues = { firstName: '' };
const getSubmitHandler =
() =>
(values, formik) => {
console.log({ values, formik });
alert(JSON.stringify(values, null, 2));
formik.setSubmitting(false);
};
return (
<div>
<Formik
initialValues={initialValues}
onSubmit={getSubmitHandler()}
>
{(formik) => (
<Form>
<label htmlFor="firstName">First Name</label>
<Field id="firstName" name="firstName" placeholder="First Name" />
<button
type="button"
onClick={(event) =>
getSubmitHandler()(formik.values, formik)
}
>
Submit
</button>
</Form>
)}
</Formik>
</div>
);
}

Related

Not able to write into textfield

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

React - Unhandled Rejection (TypeError): e.preventDefault is not a function

I'm getting this error below when I try to implement an Axios post with react-hook-form:
Unhandled Rejection (TypeError): e.preventDefault is not a function
The problem started occuring when I added onSubmit={handleSubmit(handleSubmitAxios)} to my <form>. Basically, I want my form to be controlled by react-hook-form, using my custom handleSubmitAxios post call that communicates with my backend.
This is for my sign-in component, currently just testing out the functionality of react-hook-form however the e.preventDefault message is confusing me.
export default function SignIn() {
const { register, control, errors: fieldsErrors, handleSubmit } = useForm();
const history = useHistory();
const initialFormData = Object.freeze({
email: "",
password: "",
});
const [formData, updateFormData] = useState(initialFormData);
const handleChange = (e) => {
updateFormData({
...formData,
});
};
const dispatch = useDispatch();
const handleSubmitAxios = (e) => {
e.preventDefault();
console.log(formData);
axiosInstance
.post(`auth/token/`, {
grant_type: "password",
username: formData.email,
password: formData.password,
})
.then((res) => {
console.log(res);
localStorage.setItem("access_token", res.data.access_token);
localStorage.setItem("refresh_token", res.data.refresh_token);
history.push("/");
window.location.reload();
dispatch(
login({
name: formData.email,
password: formData.password,
loggedIn: true,
})
);
});
};
const classes = useStyles();
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form
className={classes.form}
noValidate
onSubmit={handleSubmit(handleSubmitAxios)}
>
<FormControl fullWidth variant="outlined">
<Controller
name="email"
as={
<TextField
variant="outlined"
margin="normal"
inputRef={register}
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
onChange={handleChange}
helperText={
fieldsErrors.email ? fieldsErrors.email.message : null
}
error={fieldsErrors.email}
/>
}
control={control}
defaultValue=""
rules={{
required: "Required",
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
message: "invalid email address",
},
}}
/>
</FormControl>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Sign In
</Button>
</form>
</div>
</Container>
);
}
Thank you for any help or guidance on how I can solve the original error!
As per the docs, First parameter is the data or errors object, second parameter is the form event.
((data: Object, e?: Event) => void, (errors: Object, e?: Event) => void) => Function
In your case is e is the data, that's why you're getting e.preventDefault is not a function error.
Try like this
const handleSubmitAxios = (data, e) => {
e.preventDefault(); // Actually, you don’t need to call this, Because it’s already called inside react hook form.
console.log(data)
.....
But, The react-hook-form is already preventing the default form event, Not sure why you wanna do that again. Check this screen shot once and demo too

React + Ant design login form e.preventDefault is not a function

I have been trying to implement a login page functionality in react using Ant Desing forms.
I keep getting the following error
I tried checking all the semicolons to see if this is due to a missing semicolon but was unable to find the root cause.
TypeError: e.preventDefault is not a function
at onSubmit (LoginForm.js:27)
at onFinish (LoginForm.js:44)
at onFinish (Form.js:73)
at useForm.js:811
Here is my code for the login page.
import React, {useState} from 'react';
import {Link, Redirect} from 'react-router-dom';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import styles from './NormalLoginForm.css';
import { Form, Input, Button, Checkbox, Layout, Card } from 'antd';
import { UserOutlined, LockOutlined } from '#ant-design/icons';
import {login} from '../auth/actions/auth';
const NormalLoginForm = ({ login, isAuthenticated }) => {
const [formData, setFormData] = useState({
username: '',
password:''
});
const { username, password} = formData;
const onChange = e => setFormData({...formData, [e.target.name]: e.target.value});
const onFinish = (values) => {
console.log('Received values of form onFinish: ', values);
// login(username,password)
};
const onSubmit = e => {
e.preventDefault();
console.log('Received values of form(onSumbit):' );
login(username,password);
};
if (isAuthenticated)
return <Redirect to='/' />;
return (
<Form
name="normal_login"
className="login-form"
initialValues={{
remember: true,
}}
onFinish={e=> onSubmit(e)}
style={{ maxWidth: 300, align: 'center'}}
>
<Form.Item
name="username"
rules={[
{
required: true,
message: 'Please input your Username!',
},
]}
>
<Input prefix={<UserOutlined className="site-form-item-icon" />}
placeholder="Username"
onChange={e=> onChange(e)}
/>
</Form.Item>
<Form.Item
name="password"
rules={[
{
required: true,
message: 'Please input your Password!',
},
]}
>
<Input
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Password"
onChange={e => onChange(e)}
/>
</Form.Item>
<Form.Item>
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<a className="login-form-forgot" href="">
Forgot password
</a>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
{/*Or register now!*/}
</Form.Item>
</Form>
);
};
NormalLoginForm.propTypes = {
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool
};
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated
});
connect(mapStateToProps, { login })(NormalLoginForm);
function LoginForm () {
return (
<NormalLoginForm/>
)
}
export default LoginForm;
Where am I making the mistake? this is my first project in React, been stuck here for weeks. Kindly help.
If you want to call preventDefault when the form gets submitted, use an onSubmit handler instead. onFinish's parameter is not the event, but the values.
onSubmit={e => e.preventDefault()}
onFinish={onFinish}
const onFinish = (values) => { // you can remove this parameter if you don't use it
login(username, password);
};

Using react-private-route when token is in local storage

I am trying to create private routes using react-private-route to lead a user to an authorized /panel page if the authentication is successful:
https://github.com/hansfpc/react-private-route
For authentication, the submit button sends a mutation request to the graphql backend. If the authentication is succesful, the backend then sends back a token, which is stored in localStorage.
Here's what my code for my login page looks like: Do I need to make amends here?
const LoginMutation = gql`
mutation LoginMutation($email: String!, $password: String!) {
loginEmail(email: $email, password: $password)
}
`;
const schema = Yup.object({
email: Yup
.string()
.email('Invalid Email')
.required('Please Enter your Email'),
password: Yup
.string()
.required('Please Enter your password')
});
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
loggedIn: false,
});
function submitForm(LoginMutation: any) {
const { email, password } = state;
console.log(email, password)
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { email, password },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<form style={{ width: '100%' }} onSubmit={e => {e.preventDefault();submitForm(LoginMutation)}}>
<TextField
variant="outlined"
margin="normal"
id="email"
fullWidth
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="password"
name="password"
helperText={touched.password ? errors.password : ""}
error={touched.password && Boolean(errors.password)}
label="Password"
type="password"
value={password}
onChange={change.bind(null, "password")}
/>
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
>
Submit</Button>
</form>
)
}}
</Formik>
</div>
)
}
</Mutation>
);
}
export default LoginPage;
Now I don't quite understand how I should modify this code in a way that I can set my private route if the token is being returned.
Some conditional statements perhaps but how and where? The example on the GitHub repo of react-private-route has isAuthenticated={!!isLoggedIn() /* this method returns true or false */}property. How can I create this function according to my code?
Here are few example solutions for Typescript but I don't understand how to use them with local storage How to rewrite the protected/private route using TypeScript and React-Router 4 and 5?
You can use like this if you have token stored in localstorage.
import { Redirect } from "react-router";
const PrivateRoute = ({ component: Component, ...props }) => {
return (
<Route
{...props}
render={innerProps =>
localStorage.getItem("Token") ? (
<Component {...innerProps} />
) : (
<Redirect
to={{
pathname: "/",
state: { from: props.location }
}}
/>
)
}
/>
);

How to pass input from React Form to Axios get request and display the request result?

I am trying to take input from a user and pass that input into an axios get, when this is done the resultant information is passed into an array that will be displayed as cards.
I am having an issue where the code below is compiling but when enter values into the form fields and press submit, nothing occurrs. Nothing shows up in the web console either.
Where abouts am I going wrong?
const initial_state = {
location: "",
cuisine: "",
searchResults: []
};
class SearchMealForm extends Component {
constructor(props) {
super(props);
this.state = { ...initial_state };
}
//handle user input and inject it into yelp api get request
handleSubmit = event => {
event.preventDefault();
const { location, cuisine, searchResults} = this.state;
this.props.onFormSubmit(this.state(location, cuisine, searchResults));
axios.get(`https://api.yelp.com/v3/businesses/search?location=${location}+IE&categories=${cuisine}`)
.then(response => this.setState({searchResults}))
};
handleChange = event => {
this.setState({ [event.target.name]: event.target.value });
};
//YELP http api get request
searchYelpRestaurants = (location, cuisine, searchResults) => {
axios
.get(
`https://api.yelp.com/v3/businesses/search?location=${location}+IE&categories=${cuisine}`,
{
headers: {
Authorization: `Bearer ${process.env.REACT_APP_API_KEY_YELP}`
}
}
)
.then(res => this.setState({ searchResults: res.data.businesses }));
};
render() {
const { location, cuisine } = this.state;
//create cards with the results from the Yelp API GET
const YelpSearchResults = this.state.searchResults.map(result => {
return (
<div className="ResultCard">
<Card style={{ width: "18rem" }}>
<Card.Img variant="top" src={result.image_url} />
<Card.Body>
<Card.Title>{result.name}</Card.Title>
</Card.Body>
<ListGroup className="list-group-flush">
<ListGroupItem>{result.categories}</ListGroupItem>
<ListGroupItem>{result.rating}</ListGroupItem>
</ListGroup>
<Button variant="primary">Book restaurant</Button>
</Card>
</div>
);
});
// return YelpSearchResults;
// }
return (
<React.Fragment>
<div className="SearchMeal">
<Form onSubmit={this.handleSubmit}>
<Form.Row>
<Form.Group as={Col}>
<Form.Label>City</Form.Label>
<Form.Control
name="location"
type="text"
value={location}
onChange={this.handleChange}
placeholder="location"
/>
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col}>
<Form.Label>Cuisine</Form.Label>
<Form.Control
name="cuisine"
type="text"
value={cuisine}
onChange={this.handleChange}
placeholder="cuisine"
/>
</Form.Group>
</Form.Row>
<Button>Submit</Button>
<Button>Clear</Button>
</Form>
</div>
{YelpSearchResults}
</React.Fragment>
);
}
}
export default SearchMealForm;
here Button type should be specified as submit
<Button type="submit">Submit</Button>
in order for form submit to work!
I would refactor your component to be functional
import React, { useState } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import {
Col,
Form,
Card,
Button,
ListGroup,
ListGroupItem
} from "react-bootstrap";
const initial_state = {
location: "",
cuisine: "",
searchResults: []
};
const SearchMealForm = ({ onFormSubmit = () => {} }) => {
const [state, setState] = useState(initial_state);
//handle user input and inject it into yelp api get request
const handleSubmit = async event => {
event.preventDefault();
const { location, cuisine } = state;
onFormSubmit(state);
const searchResults = await searchYelpRestaurants({ location, cuisine });
setState({ ...state, searchResults });
};
const handleChange = event => {
setState({
...state,
[event.target.name]: event.target.value
});
};
//YELP http api get request
const searchYelpRestaurants = async ({ location, cuisine }) => {
try {
const { data: { businesses: searchResults } = {} } = await axios.get(
`https://api.yelp.com/v3/businesses/search?location=${location}+IE&categories=${cuisine}`,
{
headers: {
Authorization: `Bearer dBMqyRFmBBg7DMZPK9v3rbGHmrLtlURpNUCJP6gbYHtyHTmboF-Mka-ZkHiDNq-G9ktATohJGD5iKQvelOHg3sDorBDiMgnsaa8SzTH8w6hjGQXlaIexDxFlTW3FXXYx`
}
}
);
return searchResults;
} catch (err) {
console.error(err);
}
return [];
};
const { location, cuisine, searchResults } = state;
//create cards with the results from the Yelp API GET
const YelpSearchResults = searchResults.map(result => (
<div className="ResultCard">
<Card style={{ width: "18rem" }}>
<Card.Img variant="top" src={result.image_url} />
<Card.Body>
<Card.Title>{result.name}</Card.Title>
</Card.Body>
<ListGroup className="list-group-flush">
<ListGroupItem>{result.categories}</ListGroupItem>
<ListGroupItem>{result.rating}</ListGroupItem>
</ListGroup>
<Button variant="primary">Book restaurant</Button>
</Card>
</div>
));
return (
<React.Fragment>
<div className="SearchMeal">
<Form onSubmit={handleSubmit}>
<Form.Row>
<Form.Group as={Col}>
<Form.Label>City</Form.Label>
<Form.Control
name="location"
type="text"
value={location}
onChange={handleChange}
placeholder="location"
/>
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col}>
<Form.Label>Cuisine</Form.Label>
<Form.Control
name="cuisine"
type="text"
value={cuisine}
onChange={handleChange}
placeholder="cuisine"
/>
</Form.Group>
</Form.Row>
<Button type="submit">Submit</Button>
<Button>Clear</Button>
</Form>
</div>
{YelpSearchResults}
</React.Fragment>
);
};
export default SearchMealForm;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Categories

Resources