react-hook-form onSubmit not triggered - javascript

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

Related

How to set initial state with most up to date data returned from API?

I'm making a simple form to edit an app, the initial state of the name & description of the app is set using the data returned from the API.
Currently, when submitting the form the initial data seems to be logging as undefined, the name & description is being set as undefined which occurs in the first render (I have commented in the code where the logs are)
How can I make sure the initial state of name & description has the most up to date information?
Is the excessive renders the problem?
Thanks for taking a look, any help would be appreciated.
import React, { useState, useContext, useEffect } from "react";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Container from "#material-ui/core/Container";
import SaveIcon from "#mui/icons-material/Save";
import CloseIcon from "#mui/icons-material/Close";
import { makeStyles } from "#material-ui/styles";
import TextField from "#material-ui/core/TextField";
import { Grid } from "#mui/material";
import { useDispatch } from "react-redux";
import { updateApp, updateSelectedApp } from "../../services/reducers/apps";
import { EndpointContext } from "../../baxios/EndpointProvider";
import { useParams } from "react-router-dom";
export default function EditApp() {
const { appid } = useParams();
const classes = useStyles();
const dispatch = useDispatch();
const endpoints = useContext(EndpointContext);
const [selectedApp, setSelectedApp] = useState({});
const [isLoaded, setIsLoaded] = useState(false); // <--- Is there anyway I can also remove this useState? without this the default values in the forms dont populate
useEffect(() => {
async function fetchApp() {
await endpoints.appEndpoints.get(appid).then((response) => {
if (response.status === 200) {
setSelectedApp(response.data);
setIsLoaded(true);
}
});
}
fetchApp();
}, []);
useEffect(() => {
console.log(selectedApp);
}, [selectedApp]);
const [name, setName] = useState(selectedApp.name);
const [description, setDescription] = useState(selectedApp.description);
console.log("---", name, selectedApp.name); // <--- The page renders 3 times, each render log looks like this
// 1st render - --- undefined, undefined
// 2nd render - --- undefined, Appname
// 3rd render - --- undefined, Appname
const handleSubmit = (e) => {
e.preventDefault();
console.log("triggered", name, description); // <--- This logs (triggered, undefined, undefined)
if (name && description) {
const body = { name: name, description: description };
endpoints.appEndpoints.put(selectedApp.id, body).then((response) => {
if (response.status === 200) {
dispatch(updateApp(response.data));
setSelectedApp(response.data);
setName(selectedApp.name);
setDescription(selectedApp.description);
}
});
}
};
return (
<div style={{ margin: 100, marginLeft: 350 }}>
{isLoaded ? (
<Container size="sm" style={{ marginTop: 40 }}>
<Typography
variant="h6"
color="textSecondary"
component="h2"
gutterBottom
>
Edit App
</Typography>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
</Container>
) : (
<></>
)}
</div>
);
}
When using const [name, setName] = useState(defaultName), if the defaultName is updated in a future render, then the name value will not be updated to the this latest value.
So in your case you can make the following changes :
const [name, setName] = useState();
const [description, setDescription] = useState();
useEffect(() => {
setName(selectedApp.name)
setDescription(selectedApp.description)
}, [selectedApp])
)
Name and Description are undefined
Your selectedApp is initialized as an empty object. Your useEffect fires a request off to retrieve that data, but the page renders once before it gets the response. There are a couple of ways to handle this. You could do anything from displaying a loading icon on the field, to having a default value for the field until your useEffect with [selectedApp] is called. Once that information is retrieved and sent back, your information will be up to date in state, but if you need to store it for later, you'll need to build out a function to save that data.
Default value:
const [name, setName] = useState(selectedApp.name ?? "Your default value here");
const [description, setDescription] = useState(selectedApp.description ?? "Your default value here");
Loading icon:
{selectedApp ? (
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<TextField
className={classes.field}
onChange={(e) => setName(e.target.value)}
label="App Name"
variant="outlined"
color="secondary"
fullWidth
required
size="small"
defaultValue={selectedApp.name}
error={nameError}
/>
<TextField
className={classes.field}
onChange={(e) => setDescription(e.target.value)}
label="Description"
variant="outlined"
color="secondary"
rows={4}
fullWidth
required
size="small"
defaultValue={selectedApp.description}
error={descriptionError}
/>
<Grid container spacing={2}>
<Grid item>
<Button
// onClick={handleSubmit}
type="submit"
color="primary"
variant="contained"
endIcon={<SaveIcon />}
>
Save
</Button>
</Grid>
</Grid>
</form>
) : <LoadingIconComponent/>}

Use default Material-UI validation in form submit

I'm trying to implement a form with validation:
import React from 'react';
import { useForm } from "react-hook-form";
import axios, {AxiosResponse} from "axios";
import {Box, Button, Container, Grid, Typography} from "#material-ui/core";
import TextField from '#material-ui/core/TextField';
export async function postTicket(data: TicketDTO): Promise<AxiosResponse<TicketDTO[]>> {
return await axios.post<TicketDTO[]>(
`http://localhost:8080/api/support/tickets/create`
);
}
export interface TicketDTO {
title?: string;
}
export default function OpenTicket(props: any) {
const {
register,
handleSubmit,
formState: { errors }
} = useForm<TicketDTO>();
const onSubmit = async (data: TicketDTO) => {
try {
await postTicket(data);
} catch (err) {
console.log(err);
}
};
return (
<Container>
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
id="outlined-full-width"
label="Name"
{...register("title", { required: true, maxLength: 20 })}
placeholder="Placeholder"
helperText="Full width!"
fullWidth
margin="normal"
InputLabelProps={{
shrink: true,
}}
variant="outlined"
/>
<Button
variant="contained"
color="primary"
size="small"
>
Submit
</Button>
</form>
</Container>
);
}
Sandbox: https://stackblitz.com/edit/react-ts-4pnf8b?file=Hello.tsx
But when I click submit button nothing happens. I would like to use the Materia-ui default validation messages? Do you know where I'm wrong?
I have made some modifications to your code,
Added type to your button as submit
required can be directly added to Text-Field.
maxLength can be applied as part of inputProps.
I am not quite sure what you are doing in your submit event. Ignoring for now.
https://stackblitz.com/edit/react-ts-dufrzd?file=Hello.tsx
<form onSubmit={handleSubmit(onSubmit)}>
<TextField
id="outlined-full-width"
label="Name"
required
placeholder="Placeholder"
helperText="Full width!"
fullWidth
margin="normal"
InputLabelProps={{
shrink: true,
}}
inputProps={{maxLength:20}}
variant="outlined"
/>
<Button variant="contained" color="primary" size="small" type="submit">
Submit
</Button>
</form>

React - clearing an form data after form submit

My React form doesn't clear after submitting the form-data. where is the glitch? I can see form is successfully submitted to the mongoDB database with all the form content but problem is after submitting the value it doesn't clear the input filled.
import React,{useState} from 'react'
import { Paper, Typography, Button } from "#material-ui/core";
import FileBase from "react-file-base64";
import useStyles from "./styles"
import InputField from './InputField';
import { createPost } from "../../redux/actions/postAction";
import {useDispatch} from "react-redux";
const Form = () => {
const classes = useStyles();
const dispatch = useDispatch();
const [formData, setFormData] = useState({creator : "", title : "", message : "", tags : "", selectedFile : ""});
const handleSubmit = async (e) => {
e.preventDefault();
try {
dispatch(createPost(formData));
clearForm()
} catch (error) {
console.log(error);
}
}
const handleChange = (e) => {
setFormData({ ...formData, tags : e.target.value.split(",") , [e.target.name] : e.target.value })
}
const clearForm = () => {
setFormData({ creator : "", title : "", message : "", tags : "", selectedFile : ""})
}
return (
<Paper className={classes.paper}>
<Typography variant="h6" align="center">CREATE POST</Typography>
<form onSubmit={handleSubmit}>
<InputField
name="creator"
label="Creator"
handleChange={handleChange}
/>
<InputField
name="title"
label="Title"
handleChange={handleChange}
/>
<InputField
name="message"
label="Message"
handleChange={handleChange}
/>
<InputField
name="tags"
label="Tags"
handleChange={handleChange}
/>
<FileBase
type="file"
multiple={false}
onDone={ ({base64}) => setFormData({...formData, selectedFile : base64}) }
/>
<Button className={classes.btn} type="submit" variant="contained" color="primary" size="small" fullWidth>Create Posts</Button>
<Button variant="contained" color="secondary" size="small" fullWidth onClick={ clearForm }>Clear</Button>
</form>
</Paper>
)
}
export default Form
Try awaiting the dispatch before running clearForm().
EX:
try {
await dispatch(createPost(formData));
clearForm()
} catch (error) {
console.log(error);
}
}

react-hook-form is leaving the input field while typing with mode property set

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 ?

A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled

This is SignIn Component. I am using Firebase concept. Material UI for designing purpose. I am using functional component.
Is it proper way to authenticate the user?I facing an error.
import React, { useState } from "react";
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import CssBaseline from "#material-ui/core/CssBaseline";
import TextField from "#material-ui/core/TextField";
import LockOutlinedIcon from "#material-ui/icons/LockOutlined";
import Typography from "#material-ui/core/Typography";
import { makeStyles } from "#material-ui/core/styles";
import Container from "#material-ui/core/Container";
import Firebase from "../services/Firebase";
const Signin = () => {
const [values, setValues] = useState({email: "",password: ""})
const useStyles = makeStyles(theme => ({
paper: {
marginTop: theme.spacing(8),
display: "flex",
flexDirection: "column",
alignItems: "center"
},
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
},
form: {
width: "100%", // Fix IE 11 issue.
marginTop: theme.spacing(1)
},
submit: {
height: 48,
padding: "0 15px",
margin: theme.spacing(7)
}
}));
const classes = useStyles();
const signin = (e) => {
e.preventDefault();
Firebase.auth().signInWithEmailAndPassword(values.email, values.password).then((u) => {
console.log(u)
}).catch((err) => {
console.log(err);
})
}
const signup = (e) => {
e.preventDefault();
Firebase.auth().createUserWithEmailAndPassword(values.email, values.password).then((u) => {
console.log(u)
}).catch((err) => {
console.log(err);
})
}
const handleChange = (e) => {
setValues({
[e.target.name]: e.target.value
})
}
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
placeholder="Enter Email.."
onChange={(e) => handleChange(e)}
value={values.email}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
placeholder="Enter Password.."
type="password"
onChange={(e) => handleChange(e)}
value={values.password}
/>
<Button
type="submit"
margin="normal"
variant="contained"
color="primary"
className={classes.submit}
onClick={(e) => signin(e)}
>
Sign In
</Button>
<Button
type="submit"
margin="normal"
variant="contained"
color="primary"
className={classes.submit}
onClick={(e) => signup(e)}
>
Sign Up
</Button>
</form>
</div>
</Container>
)
}
export default Signin;
This Firebase Component
import firebase from 'firebase';
require('firebase/app')
require('firebase/auth')
const Firebase = firebase.initializeApp({
apiKey: "AIzaSyC_3KRb7H0Xw1-DGfqAzqfxeZaw3W5PaLg",
authDomain: "my-login-page-react.firebaseapp.com",
databaseURL: "https://my-login-page-react.firebaseio.com",
projectId: "my-login-page-react",
storageBucket: "my-login-page-react.appspot.com",
messagingSenderId: "415587749418",
appId: "1:415587749418:web:ee026252bc0a64c1a57d53"
});
export default Firebase;
This will delete the initial object key-pairs making the value={values.*} in TextField uncontrolled.
setValues({
[e.target.name]: e.target.value
})
To override keeping earlier object key pairs use spread operation -
setValues({...values,
[e.target.name]: e.target.value
})
Problem might be caused by the way you keep values. You handle 2 values with 1 hook. When you call
setValues({
[e.target.name]: e.target.value
})
It probably overrides the previous values which had 2 values with 1 value, either e-mail or password so one of them becomes unidentified and
//This is an uncontrolled component
<TextField
variant="outlined"
margin="normal"
required
fullWidth
placeholder="Enter Email.."
onChange={(e) => handleChange(e)}
value={unidentified}
/>
Try to seperate your values as:
[email,setEmail] : useState("");
[password,setPassword] : useState("")

Categories