Reset Form After Submitting - javascript

I have a react form that calls a graphql mutation when the button is clicked. After the button is clicked and the mutation is completed, the text in the form is still there. So in order to run a new mutation, the user will have to manually remove the text written in the text fields.
Is there any way to automatically reset the text fields once the form is submitted?
export default function RemoveUserPage() {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isRemoved, setIsRemoved] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [removeUser] = useMutation<DeleteUserReponse>(REMOVE_USER);
let submitForm = (email: string) => {
setIsSubmitted(true);
removeUser({
variables: {
email: email,
},
})
.then(({ data }: ExecutionResult<DeleteUserReponse>) => {
if (data !== null && data !== undefined){
setIsRemoved(true);
}})
.catch((error: { message: string }) => {
setIsRemoved(false);
setErrorMessage(error.message);
});
};
const initialValues={ email: '' }
return (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<Formik
//initialValues={{ email: '' }}
initialValues={initialValues}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
initialValues={initialValues}
}}
validationSchema={schema}>
{props => {
const {
values: { email },
errors,
touched,
handleChange,
isValid,
setFieldTouched,
} = props;
const change = (name: string, e: FormEvent) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
return (
<div>
<form
style={{ width: '100%' }}
onSubmit={e => {
e.preventDefault();
submitForm(email);
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ''}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, 'email')}
/>
<CustomButton
disabled={!isValid || !email}
text={'Remove User'}
/>
</div>
</form>
{isSubmitted && StatusMessage(isRemoved, errorMessage)}
</div>
);
}}
</Formik>
</div>
);
}

Try reusing your initial state, something like this:
const INITIAL_STATE = { email: '' };
export default function RemoveUserPage() {
const [isSubmitted, setIsSubmitted] = useState(false);
const [isRemoved, setIsRemoved] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [removeUser] = useMutation<DeleteUserReponse>(REMOVE_USER);
let submitForm = (email: string) => {
setIsSubmitted(true);
removeUser({
variables: {
email: email,
},
})
.then(({ data }: ExecutionResult<DeleteUserReponse>) => {
if (data !== null && data !== undefined){
setIsRemoved(true);
}})
.catch((error: { message: string }) => {
setIsRemoved(false);
setErrorMessage(error.message);
});
};
return (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<Formik
initialValues={{ ...INITIAL_STATE }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
actions.setValues({ ...INITIAL_STATE });
}}
validationSchema={schema}>
{props => {
const {
values: { email },
errors,
touched,
handleChange,
isValid,
setFieldTouched,
} = props;
const change = (name: string, e: FormEvent) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
};
return (
<div>
<form
style={{ width: '100%' }}
onSubmit={e => {
e.preventDefault();
submitForm(email);
}}>
<div>
<TextField
variant="outlined"
margin="normal"
id="email"
name="email"
helperText={touched.email ? errors.email : ''}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, 'email')}
/>
<CustomButton
disabled={!isValid || !email}
text={'Remove User'}
/>
</div>
</form>
{isSubmitted && StatusMessage(isRemoved, errorMessage)}
</div>
);
}}
</Formik>
</div>
);
}
Although I've also seen resetForm method on second parameter of onSubmit, but haven't tested it how it works, so you can try that also.

Related

Formik field values arn't being passed from React Context

I have a Formik form that is using a progressive stepper and have multiple fields across different components, thus requiring the need to store the values in React Context. However none of the field values are being passed, so when I click submit, all values are empty strings and the validation fails. You can see on each Formik Field i am setting the value as {state.[field]}, which comes from the Context, so I believe something is going wrong here. Can anyone see what I'm doing wrong?
Thanks a lot
Here is my parent component
const AddSongPage = () => {
const { state, dispatch } = useUploadFormContext();
const initialValues = {
name: "",
};
const { mutate: handleCreateTrack } = useCreateSyncTrackMutation(
gqlClient,
{}
);
const handleSubmit = (values: any) => {
handleCreateTrack(
{
...values,
},
{
onSuccess() {
console.log("Track added succesfully");
},
}
);
};
const validate = Yup.object({
name: Yup.string().required("Song name is required"),
description: Yup.string().optional(),
});
return (
<Layout headerBg="brand.blue">
<Formik
onSubmit={(values) => handleSubmit(values)}
initialValues={initialValues}
validationSchema={validate}
>
<Form>
<Box> {state.activeStep === 1 && <Step1 />}</Box>
<Box> {state.activeStep === 2 && <Step2 />}</Box>
<Box> {state.activeStep === 3 && <Step3 />}</Box>
<Button type={"submit"}>Submit</Button>
</Form>
</Formik>
</Layout>
);
};
Here is step 1
const Step1 = () => {
const { state, dispatch } = useUploadFormContext();
const onInputChange = (e: FormEvent<HTMLInputElement>) => {
const inputName = e.currentTarget.name;
dispatch({
type: "SET_UPLOAD_FORM",
payload: {
[inputName]: e.currentTarget.value,
},
});
};
return (
<Stack spacing={4}>
<Field name={"name"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.name}
/>
</FormControl>
)}
</Field>
<Field name={"description"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.description}
/>
</FormControl>
)}
</Field>
</Stack>
);
};
export default Step1;

Getting undefined values from Input Value in ReactJS

I am unable to get the values of email and password from the FormControl Input values. I am using the BaseWeb ReactUI framework for the field UI.
Need help stuck on this issue.
import { FormControl } from 'baseui/form-control';
import { Input } from 'baseui/input';
import { useStyletron } from 'baseui';
import { Alert } from 'baseui/icon';
import { Button } from 'baseui/button';
import { TiUserAddOutline } from "react-icons/ti";
import Axios from "axios";
import { useHistory, Link } from 'react-router-dom';
import { validate as validateEmail } from 'email-validator'; // add this package to your repo with `$ yarn add email-validator`
import { Select } from 'baseui/select';
import { Checkbox } from 'baseui/checkbox';
function SelectAtStart(props) {
const [css] = useStyletron();
return (
<div className={css({ display: 'flex' })}>
<div className={css({ width: '200px', paddingRight: '8px' })}>
<Select
options={props.options}
labelKey="id"
valueKey="gender"
onChange={({ value }) => props.onSelectChange(value)}
value={props.selectValue}
id={props.id}
/>
</div>
<Input
onChange={e => props.onInputChange(e.target.value)}
value={props.inputValue}
/>
</div>
);
}
function Negative() {
const [css, theme] = useStyletron();
return (
<div
className={css({
display: 'flex',
alignItems: 'center',
paddingRight: theme.sizing.scale500,
color: theme.colors.negative400,
})}
>
<Alert size="18px" />
</div>
);
}
export function RegisterFields() {
const history = useHistory();
const [css, theme] = useStyletron();
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const [checked, setChecked] = React.useState(true);
const [startInputValue, setStartInputValue] = React.useState('');
const [startSelectValue, setStartSelectValue] = React.useState(
[],
);
const [isValid, setIsValid] = React.useState(false);
const [isVisited, setIsVisited] = React.useState(false);
const shouldShowError = !isValid && isVisited;
const onChangeEmail = ({ target: { email } }) => {
setIsValid(validateEmail(email));
setEmail(email);
};
const onChangePassword = ({ target: { password } }) => {
setIsValid(true);
setPassword(password);
}
const handleSubmit = (event) => {
event.preventDefault();
console.log(email, password)
Axios.defaults.headers.common = {
"Content-Type": "application/json"
}
Axios.post("http://localhost:5000/api/signup", {
email: email,
password: password,
firstName: startInputValue,
lastname: startInputValue,
agreement: checked
}).then((res) => {
if (res.status === 200) {
const path = '/dashboard'
history.push(path)
console.log(res)
}
else {
console.log("Unable to create account")
}
})
}
return (
<form
onSubmit={handleSubmit}
className={css({
marginTop: theme.sizing.scale1000,
})}
>
<FormControl
label="Your Email"
error={
shouldShowError
? 'Please input a valid email address'
: null
}
>
<Input
id="email"
value={email}
onChange={onChangeEmail}
onBlur={() => setIsVisited(true)}
error={shouldShowError}
overrides={shouldShowError ? { After: Negative } : {}}
type="email"
required
/>
</FormControl>
<FormControl
label="Your Password"
error={
shouldShowError
? 'Your password is incorrect'
: null
}
>
<Input
id="password"
value={password}
onChange={onChangePassword}
onBlur={() => setIsVisited(true)}
error={shouldShowError}
overrides={shouldShowError ? { After: Negative } : {}}
type="password"
required
/>
</FormControl>
<FormControl
label="Your Full Name"
>
<SelectAtStart
inputValue={startInputValue}
onInputChange={v => setStartInputValue(v)}
selectValue={startSelectValue}
onSelectChange={v => setStartSelectValue(v)}
options={[
{ id: 'Mr', gender: 'Male' },
{ id: 'Mrs', gender: 'Women' },
{ id: 'Ms', gender: 'Female' },
{ id: 'None', gender: 'Dont Say' },
]}
id="start-id"
/>
</FormControl>
<FormControl>
<Checkbox
checked={checked}
onChange={() => setChecked(!checked)}
>
<div className={css({
color:'grey'
})}
>I hereby agree to the terms and conditons of the platform.</div>
</Checkbox>
</FormControl>
<Button type="submit">
<TiUserAddOutline style={{ marginRight: '10px' }} />
Create Account
</Button>
<div>
<p style={{ marginTop: '10px', color: 'grey' }} >To go back to login, please <Link to='/'>click here</Link></p>
</div>
</form>
);
}
I believe it's because you keep setting it to the email/password value of target
which is "undefined".
e.target.email = undefined
e.target.password = undefined
Try with this approach:
const onChangeEmail = e =>
{
setIsValid(validateEmail(e.target.value));
setEmail(e.target.value);
};
const onChangePassword = e =>
{
setIsValid(true);
setEmail(e.target.value);
};
Here you can see that all text inputs are stored in a value of targeted event ( event.target.value )

How to pass a custom callback function onSubmit in Formik with React

I am trying to pass a custom callback function after a succesfull form is completed. When I try to pass my callback function, handleCount to onSubmit, Formik doesn't seem to pass this to my parent React component.
Note:
My previous approach that worked was to add onSubmit to the button: <button onSubmit={handleCount}>...</button>, but this will pass handleCount when the form still has errors.
Here is my code and architecture:
// Join.js
export default function Join({handleCount} {
<Formik
...
onSubmit={async (values, { setSubmitting }) => {
handleCount
await new Promise((r) => setTimeout(r, 500));
setSubmitting(false);
}}
}
<button type="submit">CREATE ACCOUNT</button>
//Signup.js
import Join from "../components/SignUpFlow/1-SignUp/Join";
<Join handleCount={handleCount}
/>
I was able to get it work by passing the setSubmitting function as a function instead of part of an object. The following code worked for me:
// Join.js
export default function Join({ handleCount }) {
return (
<Formik
initialValues={{ email: "", password: "" }}
validate={(values) => {
const errors = {};
if (!values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Invalid email address";
}
return errors;
}}
onSubmit={async (values, { setSubmitting }) => {
setTimeout(() => {
console.log("called");
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
isSubmitting,
}) => (
<form
onSubmit={async (values, setSubmitting) => {
handleCount();
await new Promise((response) => setTimeout(response, 500));
setSubmitting(false);
}}
>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{errors.email && touched.email && errors.email}
<input
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{errors.password && touched.password && errors.password}
<button type="submit" disabled={isSubmitting}>
CREATE ACCOUNT
</button>
</form>
)}
</Formik>
);
}
// Signup.js
function App() {
function handleCount() {
console.log("function handleCount called.");
}
return <Join handleCount={handleCount} />;
}

Redirection Not Working Because of Order of Executions of Functions

I have a login page. If login is successful and token is present in local storage, I want to redirect to a private page /panel. I am calling all functions on the onSubmit() of my form.
Here's what my code looks like:
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
}
}
function FormSubmitted(){
setSubmitted(true);
console.log('Form submitted');
}
function RedirectionToPanel(){
console.log('ha');
if(submitted && localStorage.getItem('token')){
console.log('FInall');
return <Redirect to='/panel'/>
}
}
function submitForm(LoginMutation: any) {
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{submitted && <Redirect to='/panel'/>}
{(LoginMutation: any) => (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Avatar>
<LockOutlinedIcon />
</Avatar>
<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);FormSubmitted();RedirectionToPanel()}}>
<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")}
/>
{submitted && ShowError()}
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
// onClick={handleOpen}
style={{
background: '#6c74cc',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px'
}}
>
Submit</Button>
</form>
)
}}
</Formik>
</div>
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
When I hit the submit button, I check the console for what happens inside the RedirectionToPanel() function. The first time, 'Ha' is printed but when I click on it for the second time, both 'Ha' & 'Finally' are printed. However, the redirection still doesn't happen.
If I use {submitted && <Redirect to='/panel'/>}after Mutation, I get this error on mutation:
This JSX tag's 'children' prop expects a single child of type '(mutateFunction: MutationFunction<any, Record<string, any>>, result: MutationResult<any>) => Element | null', but multiple children were provided.ts(2746)
If I use it after the return and before mutation, I get syntax errors on && and }.
As #wxker stated, you need to return <Redirect> elements in the render method in order for the redirect to actually occur. E.g. https://codesandbox.io/s/proud-rgb-d0g8e.
This is happening because submitForm and FormSubmitted both contain asynchronous operations. This means that calling them one after another followed by RedirectionToPanel does not guarantee that they execute in that order. Consider using the useEffect hook to do the redirect once the token is set.
Also, the reason why the page is not redirecting is because you are not returning the Redirect component into the DOM tree. You can fix this by inserting it with a conditional statement that checks the submitted state
//when localStorage.getItem('token') changes, execute the callback
useEffect(() => {
setSubmitted(true);
}, [localStorage.getItem('token')])
...
return (
...
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);}}>
...
//This line can be anywhere in the return. Even at the end is fine
{submitted && <Redirect to='/panel'/>}
<Container />
);
If you want to do this without useEffect, you can use setSubmitted within submitForm. But even then, you must have {submitted && <Redirect to='/panel'/>} somewhere in your DOM
function submitForm(LoginMutation: any) {
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
setSubmitted(true);
})
.catch(console.log)
}
}
return (
...
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);}}>
...
//This line can be anywhere in the return. Even at the end is fine
{submitted && <Redirect to='/panel'/>}
<Container />
);

PrivateRouting when Token in Local Storage [TypeScript]

If my login in successful, an authentication token is returned, which is stored in the local storage. Upon successful login, I want to go the a private route.
I found this code Javascript snippet but I am unable to make it work for Typescript. I don't have any isAuthenthicated property yet. How could I modify this accordingly?
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
fakeAuth.isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{pathname: '/login', state: { from: props.location }
}}/>
)
)}/>
)
Here is my login screen:
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;
There is a similar question but it doesn't answer my case since I'm storing the token in local storage.
just replace fakeAuth.isAuthenticated by your saved token which you might save it also as a global state right? so, in general, it just a boolean prop to check if the user is successfully login or not depend on that situation the user will redirect either to the login page or the protected page
This will go in your index.tsx file:
const token = localStorage.getItem('token');
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/login'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
And use this in the browser router/switch:
<PrivateRoute
path='/panel'
isAuthenticated={token}
component={PrivateContainer}
/>

Categories

Resources