I have a file called Context.js in which I have a reducer. All of these are passed to other components using the Context API
const reducer = (state, action) => {
switch (action.type) {
case "SET_NAME":
return {...state, name: action.payload}
case "SET_LASTNAME":
return {...state, lastname: action.payload}
case "SET_EMAIL":
return {...state, email: action.payload}
default:
return state
}
In numerous other components, I am trying to use Formik to work with forms, and would like to save the form info into state, so that if the form is accessed via other components, the info that has already been provided can be found there.
<label htmlFor="name">Nome</label>
<Field maxLength="51"
name="name"
value={name}
type="text"
onChange={(e) => dispatch({ type: "SET_NAME", payload: e.target.value })}
/>
<ErrorMessage name="name" />
If I try to log the 'name', it works just fine, but as I leave the field it gives me the error message as if nothing had been typed.
I can't seem to work out how to use Formik along with useReducer or how to pass its info to other components
Formik handles forms in state so you might have to implement your form like this to help you send your input data to redux reducer: I hope this example helps
import React from "react";
import { Formik } from "formik";
import { useDispatch } from 'react-redux';
const MyForm = () => {
const dispatch = useDispatch();
return (
<Formik
initialValues={{ email: "" }}
onSubmit={async values => {
await new Promise(resolve => setTimeout(resolve, 500));
alert(JSON.stringify(values, null, 2));
}}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email" style={{ display: "block" }}>
Email
</label>
<input
id="email"
placeholder="Enter your email"
type="text"
value={values.email}
onChange={(e) => {
console.log(e.target.value);
// send input data to formik
handleChange(e);
// dispatch to reducer
dispatch({ type: "SET_NAME", payload: e.target.value });
}}
onBlur={handleBlur}
className={
errors.email && touched.email
? "text-input error"
: "text-input"
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
<button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
<pre
style={{
background: '#f6f8fa',
fontSize: '.65rem',
padding: '.5rem',
}}
>
<strong>props</strong> ={' '}
{JSON.stringify(formik.values, null, 2)}
</pre>
</form>
);
}}
</Formik>
)
};
export default MyForm;
Related
Brushing up my development skills with React. I'm trying to figure a way to refactor the onSubmit property. My application is a contact form using the Formik component which sends the data to Firebase Cloudstore as well as sending an email via emailjs. If it's a success, it'll have a popup using Material UI's Snackbar. It works, but just trying to clean up the code. Please help!
onSubmit={(values, { resetForm, setSubmitting }) => {
emailjs.send("blah","blah", {
email: values.email,
name: values.name,
message: values.message
},
'blah',);
//this is sent to firebase cloudstore
db.collection("contactForm")
.add({
name: values.name,
email: values.email,
message: values.message,
})
.then(() => {
handleClick();
})
.catch((error) => {
alert(error.message);
});
setTimeout(() => {
resetForm();
setSubmitting(false);
/* console.log(values);
console.log(JSON.stringify(values, null, 2)); */
}, 500);
}}
Here's the complete function
function Contact() {
const [open, setOpen] = React.useState(false);
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
setOpen(false);
};
const handleClick = () => {
setOpen(true);
};
const classes = useStyles();
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, { resetForm, setSubmitting }) => {
emailjs.send("blah","blah", {
email: values.email,
name: values.name,
message: values.message
},
'blah',);
//this is sent to firebase cloudstore
db.collection("contactForm")
.add({
name: values.name,
email: values.email,
message: values.message,
})
.then(() => {
handleClick();
})
.catch((error) => {
alert(error.message);
});
setTimeout(() => {
resetForm();
setSubmitting(false);
/* console.log(values);
console.log(JSON.stringify(values, null, 2)); */
}, 500);
}}
>
{({ submitForm, isSubmitting }) => (
<Form>
<Snackbar open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity="success">
Your message has been sent!
</Alert>
</Snackbar>
<div>
<Field
component={TextField}
label="Name"
name="name"
type="name"
/>
<ErrorMessage name="name" />
</div>
<div>
<Field
component={TextField}
label="Your email"
name="email"
type="email"
/>
<ErrorMessage name="email" />
</div>
<br />
<br />
<div>
<Field
as="textarea"
placeholder="Your Message"
label="message"
name="message"
type="message"
rows="15"
cols="70"
/>
<ErrorMessage name="message" />
</div>
{isSubmitting && <LinearProgress />}
<Button
variant="contained"
color="primary"
disabled={isSubmitting}
onClick={submitForm}
>
Submit
</Button>
</Form>
)}
</Formik>
);
}
I would recommend making the onSubmit property it's own function in the component body, you will want to memoize this using useCallback. Additionally, you can create a hook to allow you to control the alert component, you can also allow the hook to control weather it's an error or success type, reducing the need to duplicate code if it fails to save.
Your submission handler could look like this, note that I omitted the sending of the email and mocked the firebase portion. Also you can call finally on the promise, rather than calling setSubmitting in both the then and catch blocks.
const handleSubmit = React.useCallback(
(values, { setSubmitting, resetForm }) => {
db.collection("contact")
.add(values)
.then((res) => {
show({ message: "Your message has been sent" });
})
.catch((err) => {
show({ variant: "error", message: "Failed to send your message." });
})
.finally(() => {
setSubmitting(false);
});
},
[show]
);
The show function in the above example would be part of your hook to control the alert. The hook could look something like this, it could be extended based on your usecase.
import React from "react";
const useAlert = () => {
const [state, setState] = React.useState({
variant: "success",
visibile: false,
message: null
});
const show = React.useCallback(
(options = {}) => {
setState((prev) => ({
...prev,
...options,
visible: true
}));
},
[setState]
);
const hide = React.useCallback(() => {
setState((prev) => ({ ...prev, visibile: false }));
}, [setState]);
return { ...state, show, hide };
};
export default useAlert;
Additionally, since you're using material ui, you'll want to take advantage of their built in components. This would remove the need for your multiple <br />s for spacing, as well as help to keep the UI consistent.
<Box marginBottom={1}>
<Field component={TextField} label="Name" name="name" type="name" />
<ErrorMessage name="name" />
</Box>
<Box marginBottom={1}>
<Field
component={TextField}
label="Email"
name="email"
type="email"
/>
<ErrorMessage name="email" />
</Box>
Also, you could use the built in component for the text area, keeping the design consistent. Using the multiline prop allows you to make the input a text area.
<Box marginBottom={2}>
<Field
component={TextField}
placeholder="Your Message"
label="Message"
name="message"
type="message"
rows={5}
multiline
fullWidth
/>
<ErrorMessage name="message" />
</Box>
I'm personally not a huge fan of using the LinearProgress in the manner than your did. I personally think that the circular process looks better, specifically when used inside the submit button. Here are the relevant docs.
<Button
variant="contained"
color="primary"
disabled={isSubmitting}
onClick={submitForm}
endIcon={isSubmitting && <CircularProgress size={15} />}
>
Submit
</Button>
I've put a working example together in a codesandbox.
I'm trying to incorporate Formik and React-bootstrap.
It's going as expected except the switch component.
Here is my function for rendering the switch:
function ReactSwitch(props){
return(
<>
<Col md={props.col}>
<Form.Group>
<Form.Check
type="switch"
id={props.id}
name={props.name}
label={props.label}
checked={props.checked}
onChange={props.onChange}
/>
</Form.Group>
</Col>
</>
);
}
And here is my initialization for Formik:
const formik = useFormik({
initialValues: {
wflow_switch: false
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
Note that when I change the type from switch to checkbox, it displays a checkbox but still no label. What am I doing wrong? I'm still learning React so any comments are appreciated.
I guess you'll need to use state and enable enablereinitialize
Try this:
export default function FormSwitch() {
// add checked state
const [checked, setChecked] = useState(false)
const { handleSubmit, values } = useFormik({
initialValues: {
// initial value is set 'false' by state
switch: checked
},
// Control whether Formik should reset the form if initialValues changes
enableReinitialize: true,
onSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2))
setSubmitting(false)
}, 400)
}
})
return (
<form className="form" onSubmit={handleSubmit}>
<ReactSwitch
name="switch"
label="Switch"
id="switch"
checked={checked}
onChange={() => setChecked(!checked)}
/>
<button type="submit">
Submit
</button>
</form>
)
}
Edit: Different approach using useField with <Formik>
import { Formik, useField } from "formik"
import { Col, Form } from "react-bootstrap"
const ReactSwitch = ({ ...props }) => {
const [field, form] = useField(props)
return (
<Col md={props.col}>
<Form.Group>
<Form.Check
type="switch"
id={props.id}
checked={field.value.checked}
onChange={() => form.setFieldValue(props.name, !field.value)}
{...field}
{...props}
/>
</Form.Group>
</Col>
)
}
export default function Switch() {
return (
<Formik
initialValues={{
switch: false
}}
onSubmit={values => alert(JSON.stringify(values, null, 2))}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<ReactSwitch label="Switch" name="switch" id="switch" />
<button type="submit">submit</button>
</form>
)}
</Formik>
)
}
I learn in the same time ReactJS and React Native. I saw in an udemy tutorial something very beautiful, that the professor putt it just one onChange method, for all inputs and taking advantage of the "name" attribute, he could do this:
const onChange = event =>
setFormData({ ...formData, [event.target.name]: event.target.value });
So he said, instead of having for each onChange, inside of each input, a different method, we can have only one.
This is the code that I'm talking about:
const Register = props => {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
password2: ''
});
const { name, email, password, password2 } = formData;
const onChange = event =>
setFormData({ ...formData, [event.target.name]: event.target.value });
const onSubmit = async event => {
event.preventDefault();
if (password !== password2) {
props.setAlert('Passwords do not match', 'danger', 5000);
} else {
props.registerUser({ name, email, password });
}
};
if (props.isAuthenticated) {
return <Redirect to="/dashboard" />;
}
return (
<Fragment>
<h1 className="large text-primary">Sign Up</h1>
<p className="lead">
<i className="fas fa-user" /> Create Your Account
</p>
<form className="form" onSubmit={event => onSubmit(event)}>
<div className="form-group">
<input
type="text"
placeholder="Name"
name="name"
value={name}
onChange={event => onChange(event)}
/>
</div>
<div className="form-group">
<input
type="email"
placeholder="Email Address"
name="email"
value={email}
onChange={event => onChange(event)}
/>
<small className="form-text">
This site uses Gravatar so if you want a profile image, use a
Gravatar email
</small>
</div>
<div className="form-group">
<input
type="password"
placeholder="Password"
name="password"
// minLength="6"
value={password}
onChange={event => onChange(event)}
/>
</div>
<div className="form-group">
<input
type="password"
placeholder="Confirm Password"
name="password2"
value={password2}
onChange={event => onChange(event)}
/>
</div>
<input type="submit" className="btn btn-primary" value="Register" />
</form>
<p className="my-1">
Already have an account? <Link to="/login">Sign In</Link>
</p>
</Fragment>
);
};
In React Native, that is with a different professor, I tried to think how to do this. I tried a few days the offered props from the TextInput but non of then, in my opinion, can be used how we can use the "name" attribute in ReactJS.
This is the code for the React Native app:
import React, { Component } from 'react';
import {
StyleSheet,
View,
Button,
TextInput,
} from 'react-native';
class PlaceInput extends Component {
state = {
userName: '',
placeName: ''
}
userNameChangeHandler = (value) => {
this.setState({ userName: value })
}
placeNameChangeHandler = (value) => {
this.setState({ placeName: value })
}
placeSubmitHandler = () => {
if (this.state.placeName.trim() === '') {
return;
}
this.props.onPlaceAdded(this.state.placeName)
}
render() {
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.placeInput}
value={this.state.userName}
onChangeText={this.userNameChangeHandler}
placeholder='User Name' />
<TextInput
style={styles.placeInput}
value={this.state.placeName}
onChangeText={this.placeNameChangeHandler}
placeholder='Beautiful place' />
<Button title='Add' style={styles.placeButton} onPress={this.placeSubmitHandler} />
</View>
);
}
};
Please someone help me to understand: it is possible to have one onChangeText method in React Native, like the professor from ReactJS did with onChange?
Try passing input "name" as a value to the handler function.
Like so:
import React, { Component } from 'react';
import {
StyleSheet, View, TextInput,
} from 'react-native';
class PlaceInput extends Component {
state = {
userName: '',
placeName: ''
}
handleInputChange = (inputName, inputValue) => {
this.setState(state => ({
...state,
[inputName]: inputValue // <-- Put square brackets
}))
}
render () {
return (
<View style={styles.inputContainer}>
<TextInput
style={styles.placeInput}
value={this.state.userName}
onChangeText={value => this.handleInputChange('userName', value)}
placeholder='User Name' />
<TextInput
style={styles.placeInput}
value={this.state.placeName}
onChangeText={value => this.handleInputChange('placeName', value)}
placeholder='Beautiful place' />
</View>
);
}
};
For that type of a function you are using the wrong prop. While onChangeText is all fine, it takes as its parameter a function with a single parameter: the changed text. Because of that you cannot do it with the onChangeText.
However, there exists another prop called onChange. This one supplies the following object as the parameter per the documentation: { nativeEvent: { eventCount, target, text} }. Target here, while could be used, is going to be just a number.
What would I suggest?
Instead of trying to handle it through the event.target.name change your function to take a second argument: name. After that you should call your functions as follows;
onChangeText={text => this.inputChangeHandler(text, 'name')}
This will create a function whose sole purpose is to supply the second parameter, allowing you to use just one function for all your text changes.
This is probably not the best place to post this question but where, then?
The code below is taken from Formik's overview page and I'm very confused about the onSubmit handlers:
The form element has an onSubmit property that refers to handleSubmit which is passed on that anonymous function : <form onSubmit={handleSubmit}>. Where does that come from?
The Formik component has an onSubmit property as well:
onSubmit={(values, { setSubmitting }) => { ... }
How do these relate to each other? What is going on?
import React from 'react';
import { Formik } from 'formik';
const Basic = () => (
<div>
<h1>Anywhere in your app!</h1>
<Formik
initialValues={{ email: '', password: '' }}
validate={values => {
let 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={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
/* and other goodies */
}) => (
<form onSubmit={handleSubmit}>
<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}>
Submit
</button>
</form>
)}
</Formik>
</div>
);
export default Basic;
The component takes onSubmit as a prop where you can execute code you want to perform when you submit your form. This prop is also given some arguments such as values (values of the form) for you to use in your onSubmit function.
The handleSubmit form is auto generated from the Formik library that automates some common form logics explained here. The handleSubmit will automatically execute onSubmit function mentioned above as part of its phases (pre-submit, validation, submission). Hope that answers your question!
I am using the AsyncTypeahead from the react-boostrap-typeahead library along with Formik. Both great little libraries.
A simplified version of my code looks like this
const SearchFilter = withFormik({
mapPropsToValues: (props) {
office: someIncomingValue || [{name: 'office1', id: 1}]
}
})(TheForm)
const TheForm = (props) => {
const {values, handleReset} = props;
return (
<form>
<AsyncTypeahead
defaultSelected={[values.office]}
options={...}
onChange={...SetNewValue...}
onSearch={...}/>
<button onClick={handleReset}
</form>
)
}
By using defaultSelected property on the AsynchTypeahead. i can set a default INITIAL value. But the issue i am having is when i click the button to handleRest, formik does its thing and reset the value back to office 1, but AsynchTypeahead doesnt have a way of manually passing a value back into it. So it does not change. I saw there is a selected prop available, but it just blows up when i try to use it.
Any input would be gerat
UPDATE:
Yes Selected is what i needed. i had to add an onInputChange property to keep the parent in sync with what was being typed.
I had the same question.
I came up with this solution:
import { Formik } from "formik";
import * as Yup from "yup";
import { Typeahead } from 'react-bootstrap-typeahead';
const AddCheckSchema = Yup.object().shape({
title: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required')
});
...
render() {
const users = [
{ id: 1, fullname: 'User #1' },
{ id: 2, fullname: 'User #2' },
{ id: 3, fullname: 'User #3' },
];
return (
<Formik
initialValues={{
title: '',
amount: 0,
actualDate: new Date()
}}
validationSchema={AddCheckSchema}
onSubmit={ this.onSubmit}
>
{({
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched,
}) => (
<form onSubmit={handleSubmit}>
<div className="form-group required">
<label className="control-label">Title:</label>
<Typeahead
multiple={false}
onChange={(selected) => {
const value = (selected.length > 0) ? selected[0].fullname : '';
setFieldValue('title', value);
}}
onInputChange={(text, event) => setFieldValue('title', text)}}
onBlur={(e) => setFieldTouched('title', true)}
labelKey="fullname"
options={users}
/>
<div className="text-danger">{touched.title && errors.title}</div>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary btn-lg">Add check</button>
</div>
</form>
)}
</Formik>
)
}
Of course, you can extract this complex logic (around Typeahead field) into separate component.
Reference to API.
In my case I had to opt for a simpler solution by taking a look at this example here https://ericgio.github.io/react-bootstrap-typeahead/#basic-example
export const SelectSearch = ({name, value, schema, setFieldValue, errors}) => {
const [singleSelections, setSingleSelections] = useState([]);
useEffect(() => {
if (singleSelections.length > 0) {
setFieldValue(name, singleSelections[0].value)
}
}, [singleSelections])
return (
<LabeledField name={name} schema={schema}>
<Typeahead
id="basic-typeahead-single"
multiple={false}
labelKey="label"
options={schema.choices}
onChange={setSingleSelections}
isInvalid={!!errors[name]}
placeholder="Choose a state..."
selected={singleSelections}
/>
<Form.Control.Feedback type="invalid">
{errors[name]}
</Form.Control.Feedback>
</LabeledField>
)
}
This component can then be rendered in the formik context like below
const Component = () => {
return (
<Formik
initialValues={...}
validationSchema={AddCheckSchema}
onSubmit={this.onSubmit}
>
{({
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched,
}) => (
<form onSubmit={handleSubmit}>
<div className="form-group required">
<label className="control-label">Title:</label>
<SelectSearch
name={"inputName"}
choices={choices}
setFieldValue={setFieldValue}
errors={errors}
/>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary btn-lg">Submit</button>
</div>
</form>
)}
</Formik>
)
}