diff --git a/.eslintrc b/.eslintrc index ecdc497..ceb1cb5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,7 @@ ], "rules": { "prettier/prettier": [ - "error", + 1, { "singleQuote": true } @@ -23,4 +23,4 @@ } ] } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 89b278a..8996c8a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +This project refers to the article https://medium.com/@nphivu414/build-a-multi-step-form-with-react-hooks-formik-yup-and-materialui-fa4f73545598 + This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). ## Available Scripts diff --git a/package.json b/package.json index 68645e4..be883db 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { - "name": "app", + "name": "building-multi-step-form-with-formik-and-yup", "version": "0.1.0", "private": true, "dependencies": { "@date-io/date-fns": "^1.3.13", - "@material-ui/core": "^4.9.0", - "@material-ui/pickers": "^3.2.10", + "@emotion/react": "11.7.1", + "@emotion/styled": "11.6.0", + "@mui/lab": "5.0.0-alpha.67", + "@mui/material": "5.4.0", "@testing-library/jest-dom": "^5.0.2", "@testing-library/react": "^9.4.0", "@testing-library/user-event": "^8.1.0", @@ -14,9 +16,9 @@ "lodash": "^4.17.15", "moment": "^2.24.0", "prop-types": "^15.7.2", - "react": "^16.12.0", - "react-dom": "^16.12.0", - "react-scripts": "3.3.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-scripts": "^5.0.0", "yup": "^0.28.1" }, "scripts": { @@ -41,9 +43,11 @@ ] }, "devDependencies": { - "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.18.0", - "eslint-plugin-react-hooks": "^2.3.0", - "prettier": "1.19.1" - } -} \ No newline at end of file + "eslint-plugin-react-hooks": "^4.3.0", + "prettier": "2.5.1" + }, + "keywords": [], + "description": "" +} diff --git a/src/components/CheckoutPage/CheckoutPage.jsx b/src/components/CheckoutPage/CheckoutPage.jsx index c1d5df5..6accb07 100644 --- a/src/components/CheckoutPage/CheckoutPage.jsx +++ b/src/components/CheckoutPage/CheckoutPage.jsx @@ -5,8 +5,8 @@ import { StepLabel, Button, Typography, - CircularProgress -} from '@material-ui/core'; + CircularProgress, +} from '@mui/material'; import { Formik, Form } from 'formik'; import AddressForm from './Forms/AddressForm'; @@ -18,7 +18,7 @@ import validationSchema from './FormModel/validationSchema'; import checkoutFormModel from './FormModel/checkoutFormModel'; import formInitialValues from './FormModel/formInitialValues'; -import useStyles from './styles'; +//import useStyles from "./styles"; const steps = ['Shipping address', 'Payment details', 'Review your order']; const { formId, formField } = checkoutFormModel; @@ -37,13 +37,12 @@ function _renderStepContent(step) { } export default function CheckoutPage() { - const classes = useStyles(); const [activeStep, setActiveStep] = useState(0); const currentValidationSchema = validationSchema[activeStep]; const isLastStep = activeStep === steps.length - 1; function _sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } async function _submitForm(values, actions) { @@ -73,8 +72,8 @@ export default function CheckoutPage() { Checkout - - {steps.map(label => ( + + {steps.map((label) => ( {label} @@ -93,28 +92,20 @@ export default function CheckoutPage() {
{_renderStepContent(activeStep)} -
+
{activeStep !== 0 && ( - + )} -
+
- {isSubmitting && ( - - )} + {isSubmitting && }
diff --git a/src/components/CheckoutPage/CheckoutSuccess/CheckoutSuccess.jsx b/src/components/CheckoutPage/CheckoutSuccess/CheckoutSuccess.jsx index b7f503a..e970ad7 100644 --- a/src/components/CheckoutPage/CheckoutSuccess/CheckoutSuccess.jsx +++ b/src/components/CheckoutPage/CheckoutSuccess/CheckoutSuccess.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Typography } from '@material-ui/core'; +import { Typography } from '@mui/material'; function CheckoutSuccess() { return ( diff --git a/src/components/CheckoutPage/FormModel/checkoutFormModel.js b/src/components/CheckoutPage/FormModel/checkoutFormModel.js index 5d1be8c..8d37ccf 100644 --- a/src/components/CheckoutPage/FormModel/checkoutFormModel.js +++ b/src/components/CheckoutPage/FormModel/checkoutFormModel.js @@ -1,71 +1,73 @@ -export default { +const checkoutFormModel = { formId: 'checkoutForm', formField: { firstName: { name: 'firstName', label: 'First name*', - requiredErrorMsg: 'First name is required' + requiredErrorMsg: 'First name is required', }, lastName: { name: 'lastName', label: 'Last name*', - requiredErrorMsg: 'Last name is required' + requiredErrorMsg: 'Last name is required', }, address1: { name: 'address1', label: 'Address Line 1*', - requiredErrorMsg: 'Address Line 1 is required' + requiredErrorMsg: 'Address Line 1 is required', }, address2: { name: 'address2', - label: 'Address Line 2' + label: 'Address Line 2', }, city: { name: 'city', label: 'City*', - requiredErrorMsg: 'City is required' + requiredErrorMsg: 'City is required', }, state: { name: 'state', - label: 'State/Province/Region' + label: 'State/Province/Region', }, zipcode: { name: 'zipcode', label: 'Zipcode*', requiredErrorMsg: 'Zipcode is required', - invalidErrorMsg: 'Zipcode is not valid (e.g. 70000)' + invalidErrorMsg: 'Zipcode is not valid (e.g. 70000)', }, country: { name: 'country', label: 'Country*', - requiredErrorMsg: 'Country is required' + requiredErrorMsg: 'Country is required', }, useAddressForPaymentDetails: { name: 'useAddressForPaymentDetails', - label: 'Use this address for payment details' + label: 'Use this address for payment details', }, nameOnCard: { name: 'nameOnCard', label: 'Name on card*', - requiredErrorMsg: 'Name on card is required' + requiredErrorMsg: 'Name on card is required', }, cardNumber: { name: 'cardNumber', label: 'Card number*', requiredErrorMsg: 'Card number is required', - invalidErrorMsg: 'Card number is not valid (e.g. 4111111111111)' + invalidErrorMsg: 'Card number is not valid (e.g. 4111111111111)', }, expiryDate: { name: 'expiryDate', label: 'Expiry date*', requiredErrorMsg: 'Expiry date is required', - invalidErrorMsg: 'Expiry date is not valid' + invalidErrorMsg: 'Expiry date is not valid', }, cvv: { name: 'cvv', label: 'CVV*', requiredErrorMsg: 'CVV is required', - invalidErrorMsg: 'CVV is invalid (e.g. 357)' - } - } + invalidErrorMsg: 'CVV is invalid (e.g. 357)', + }, + }, }; + +export default checkoutFormModel; diff --git a/src/components/CheckoutPage/FormModel/formInitialValues.js b/src/components/CheckoutPage/FormModel/formInitialValues.js index 8c006cf..55bf5f4 100644 --- a/src/components/CheckoutPage/FormModel/formInitialValues.js +++ b/src/components/CheckoutPage/FormModel/formInitialValues.js @@ -11,8 +11,8 @@ const { nameOnCard, cardNumber, expiryDate, - cvv - } + cvv, + }, } = checkoutFormModel; export default { @@ -26,5 +26,5 @@ export default { [nameOnCard.name]: '', [cardNumber.name]: '', [expiryDate.name]: '', - [cvv.name]: '' + [cvv.name]: '', }; diff --git a/src/components/CheckoutPage/FormModel/validationSchema.js b/src/components/CheckoutPage/FormModel/validationSchema.js index 91f66fd..f5ac5eb 100644 --- a/src/components/CheckoutPage/FormModel/validationSchema.js +++ b/src/components/CheckoutPage/FormModel/validationSchema.js @@ -12,8 +12,8 @@ const { nameOnCard, cardNumber, expiryDate, - cvv - } + cvv, + }, } = checkoutFormModel; const visaRegEx = /^(?:4[0-9]{12}(?:[0-9]{3})?)$/; @@ -23,19 +23,17 @@ export default [ [firstName.name]: Yup.string().required(`${firstName.requiredErrorMsg}`), [lastName.name]: Yup.string().required(`${lastName.requiredErrorMsg}`), [address1.name]: Yup.string().required(`${address1.requiredErrorMsg}`), - [city.name]: Yup.string() - .nullable() - .required(`${city.requiredErrorMsg}`), + [city.name]: Yup.string().nullable().required(`${city.requiredErrorMsg}`), [zipcode.name]: Yup.string() .required(`${zipcode.requiredErrorMsg}`) .test( 'len', `${zipcode.invalidErrorMsg}`, - val => val && val.length === 5 + (val) => val && val.length === 5 ), [country.name]: Yup.string() .nullable() - .required(`${country.requiredErrorMsg}`) + .required(`${country.requiredErrorMsg}`), }), Yup.object().shape({ [nameOnCard.name]: Yup.string().required(`${nameOnCard.requiredErrorMsg}`), @@ -45,7 +43,7 @@ export default [ [expiryDate.name]: Yup.string() .nullable() .required(`${expiryDate.requiredErrorMsg}`) - .test('expDate', expiryDate.invalidErrorMsg, val => { + .test('expDate', expiryDate.invalidErrorMsg, (val) => { if (val) { const startDate = new Date(); const endDate = new Date(2050, 12, 31); @@ -58,6 +56,6 @@ export default [ }), [cvv.name]: Yup.string() .required(`${cvv.requiredErrorMsg}`) - .test('len', `${cvv.invalidErrorMsg}`, val => val && val.length === 3) - }) + .test('len', `${cvv.invalidErrorMsg}`, (val) => val && val.length === 3), + }), ]; diff --git a/src/components/CheckoutPage/Forms/AddressForm.jsx b/src/components/CheckoutPage/Forms/AddressForm.jsx index c48b847..3ddeb57 100644 --- a/src/components/CheckoutPage/Forms/AddressForm.jsx +++ b/src/components/CheckoutPage/Forms/AddressForm.jsx @@ -1,62 +1,62 @@ import React from 'react'; -import { Grid, Typography } from '@material-ui/core'; +import { Grid, Typography } from '@mui/material'; import { InputField, CheckboxField, SelectField } from '../../FormFields'; const cities = [ { value: undefined, - label: 'None' + label: 'None', }, { value: '1', - label: 'New York' + label: 'New York', }, { value: '2', - label: 'Chicago' + label: 'Chicago', }, { value: '3', - label: 'Saigon' - } + label: 'Saigon', + }, ]; const states = [ { value: undefined, - label: 'None' + label: 'None', }, { value: '11', - label: 'Florida' + label: 'Florida', }, { value: '22', - label: 'Michigan' + label: 'Michigan', }, { value: '33', - label: 'Texas' - } + label: 'Texas', + }, ]; const countries = [ { value: null, - label: 'None' + label: 'None', }, { value: '111', - label: 'United States' + label: 'United States', }, { value: '222', - label: 'Italy' + label: 'Italy', }, { value: '333', - label: 'Vietnam' - } + label: 'Vietnam', + }, ]; export default function AddressForm(props) { @@ -70,8 +70,8 @@ export default function AddressForm(props) { state, zipcode, country, - useAddressForPaymentDetails - } + useAddressForPaymentDetails, + }, } = props; return ( diff --git a/src/components/CheckoutPage/Forms/PaymentForm.jsx b/src/components/CheckoutPage/Forms/PaymentForm.jsx index 387dba8..3b5cc85 100644 --- a/src/components/CheckoutPage/Forms/PaymentForm.jsx +++ b/src/components/CheckoutPage/Forms/PaymentForm.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import { Grid, Typography } from '@material-ui/core'; +import { Grid, Typography } from '@mui/material'; import { InputField, DatePickerField } from '../../FormFields'; export default function PaymentForm(props) { const { - formField: { nameOnCard, cardNumber, expiryDate, cvv } + formField: { nameOnCard, cardNumber, expiryDate, cvv }, } = props; return ( diff --git a/src/components/CheckoutPage/ReviewOrder/PaymentDetails.jsx b/src/components/CheckoutPage/ReviewOrder/PaymentDetails.jsx index 1901b41..e62cc44 100644 --- a/src/components/CheckoutPage/ReviewOrder/PaymentDetails.jsx +++ b/src/components/CheckoutPage/ReviewOrder/PaymentDetails.jsx @@ -1,15 +1,15 @@ import React from 'react'; import moment from 'moment'; -import { Typography, Grid } from '@material-ui/core'; -import useStyles from './styles'; +import { Typography, Grid } from '@mui/material'; +//import useStyles from "./styles"; function PaymentDetails(props) { const { formValues } = props; - const classes = useStyles(); + //const classes = useStyles(); const { nameOnCard, cardNumber, expiryDate } = formValues; return ( - + Payment details diff --git a/src/components/CheckoutPage/ReviewOrder/ProductDetails.jsx b/src/components/CheckoutPage/ReviewOrder/ProductDetails.jsx index 6d9da8d..621a239 100644 --- a/src/components/CheckoutPage/ReviewOrder/ProductDetails.jsx +++ b/src/components/CheckoutPage/ReviewOrder/ProductDetails.jsx @@ -1,30 +1,26 @@ import React from 'react'; -import { Typography, List, ListItem, ListItemText } from '@material-ui/core'; -import useStyles from './styles'; +import { Typography, List, ListItem, ListItemText } from '@mui/material'; const products = [ { name: 'Product 1', desc: 'A nice thing', price: '$9.99' }, { name: 'Product 2', desc: 'Another thing', price: '$3.45' }, { name: 'Product 3', desc: 'Something else', price: '$6.51' }, { name: 'Product 4', desc: 'Best thing of all', price: '$14.11' }, - { name: 'Shipping', desc: '', price: 'Free' } + { name: 'Shipping', desc: '', price: 'Free' }, ]; function ProductDetails() { - const classes = useStyles(); return ( - {products.map(product => ( - + {products.map((product) => ( + {product.price} ))} - + - - $34.06 - + $34.06 ); diff --git a/src/components/CheckoutPage/ReviewOrder/ReviewOrder.jsx b/src/components/CheckoutPage/ReviewOrder/ReviewOrder.jsx index 8308294..5d71b7c 100644 --- a/src/components/CheckoutPage/ReviewOrder/ReviewOrder.jsx +++ b/src/components/CheckoutPage/ReviewOrder/ReviewOrder.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { useFormikContext } from 'formik'; -import { Typography, Grid } from '@material-ui/core'; +import { Typography, Grid } from '@mui/material'; import ProductDetails from './ProductDetails'; import ShippingDetails from './ShippingDetails'; import PaymentDetails from './PaymentDetails'; diff --git a/src/components/CheckoutPage/ReviewOrder/ShippingDetails.jsx b/src/components/CheckoutPage/ReviewOrder/ShippingDetails.jsx index 3989ac7..519b4f8 100644 --- a/src/components/CheckoutPage/ReviewOrder/ShippingDetails.jsx +++ b/src/components/CheckoutPage/ReviewOrder/ShippingDetails.jsx @@ -1,14 +1,12 @@ import React from 'react'; -import { Typography, Grid } from '@material-ui/core'; -import useStyles from './styles'; +import { Typography, Grid } from '@mui/material'; function PaymentDetails(props) { const { formValues } = props; - const classes = useStyles(); const { firstName, lastName, address1 } = formValues; return ( - + Shipping {`${firstName} ${lastName}`} diff --git a/src/components/CheckoutPage/ReviewOrder/styles.js b/src/components/CheckoutPage/ReviewOrder/styles.js index 626176a..347e073 100644 --- a/src/components/CheckoutPage/ReviewOrder/styles.js +++ b/src/components/CheckoutPage/ReviewOrder/styles.js @@ -1,12 +1,13 @@ -import { makeStyles } from '@material-ui/core/styles'; -export default makeStyles(theme => ({ +/*import { makeStyles } from "@mui/material/styles"; +export default makeStyles((theme) => ({ listItem: { padding: theme.spacing(1, 0) }, total: { - fontWeight: '700' + fontWeight: "700" }, title: { marginTop: theme.spacing(2) } })); +*/ diff --git a/src/components/CheckoutPage/styles.js b/src/components/CheckoutPage/styles.js index 963c4d2..d5d1bbb 100644 --- a/src/components/CheckoutPage/styles.js +++ b/src/components/CheckoutPage/styles.js @@ -1,11 +1,11 @@ -import { makeStyles } from '@material-ui/core/styles'; -export default makeStyles(theme => ({ +import { makeStyles } from "@mui/material/styles"; +export default makeStyles((theme) => ({ stepper: { padding: theme.spacing(3, 0, 5) }, buttons: { - display: 'flex', - justifyContent: 'flex-end' + display: "flex", + justifyContent: "flex-end" }, button: { marginTop: theme.spacing(3), @@ -13,11 +13,11 @@ export default makeStyles(theme => ({ }, wrapper: { margin: theme.spacing(1), - position: 'relative' + position: "relative" }, buttonProgress: { - position: 'absolute', - top: '50%', - left: '50%' + position: "absolute", + top: "50%", + left: "50%" } })); diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx index c0dfc91..9b88778 100644 --- a/src/components/Footer/Footer.jsx +++ b/src/components/Footer/Footer.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Link, Typography } from '@material-ui/core/'; +import { Link, Typography } from '@mui/material/'; export default function Footer() { return ( diff --git a/src/components/FormFields/CheckboxField.jsx b/src/components/FormFields/CheckboxField.jsx index c7688ff..12e9ee1 100644 --- a/src/components/FormFields/CheckboxField.jsx +++ b/src/components/FormFields/CheckboxField.jsx @@ -5,8 +5,8 @@ import { Checkbox, FormControl, FormControlLabel, - FormHelperText -} from '@material-ui/core'; + FormHelperText, +} from '@mui/material'; export default function CheckboxField(props) { const { label, ...rest } = props; diff --git a/src/components/FormFields/DatePickerField.jsx b/src/components/FormFields/DatePickerField.jsx index 503396e..1569028 100644 --- a/src/components/FormFields/DatePickerField.jsx +++ b/src/components/FormFields/DatePickerField.jsx @@ -1,11 +1,8 @@ import React, { useState, useEffect } from 'react'; import { useField } from 'formik'; -import Grid from '@material-ui/core/Grid'; -import { - MuiPickersUtilsProvider, - KeyboardDatePicker -} from '@material-ui/pickers'; -import DateFnsUtils from '@date-io/date-fns'; +import { Grid, TextField } from '@mui/material'; +import { LocalizationProvider, DatePicker } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; export default function DatePickerField(props) { const [field, meta, helper] = useField(props); @@ -38,8 +35,8 @@ export default function DatePickerField(props) { return ( - - + } /> - + ); } diff --git a/src/components/FormFields/InputField.jsx b/src/components/FormFields/InputField.jsx index ecc68b2..0fec199 100644 --- a/src/components/FormFields/InputField.jsx +++ b/src/components/FormFields/InputField.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { at } from 'lodash'; import { useField } from 'formik'; -import { TextField } from '@material-ui/core'; +import { TextField } from '@mui/material'; export default function InputField(props) { const { errorText, ...rest } = props; diff --git a/src/components/FormFields/SelectField.jsx b/src/components/FormFields/SelectField.jsx index 35ca7e2..3a4b608 100644 --- a/src/components/FormFields/SelectField.jsx +++ b/src/components/FormFields/SelectField.jsx @@ -7,8 +7,8 @@ import { FormControl, Select, MenuItem, - FormHelperText -} from '@material-ui/core'; + FormHelperText, +} from '@mui/material'; function SelectField(props) { const { label, data, ...rest } = props; @@ -38,11 +38,11 @@ function SelectField(props) { } SelectField.defaultProps = { - data: [] + data: [], }; SelectField.propTypes = { - data: PropTypes.array.isRequired + data: PropTypes.array.isRequired, }; export default SelectField; diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx index 6103fc9..57f277e 100644 --- a/src/components/Header/Header.jsx +++ b/src/components/Header/Header.jsx @@ -1,14 +1,11 @@ import React from 'react'; -import AppBar from '@material-ui/core/AppBar'; -import Toolbar from '@material-ui/core/Toolbar'; -import Typography from '@material-ui/core/Typography'; -import useStyles from './styles'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; export default function Header() { - const classes = useStyles(); - return ( - + Company name diff --git a/src/components/Header/styles.js b/src/components/Header/styles.js deleted file mode 100644 index c5bc803..0000000 --- a/src/components/Header/styles.js +++ /dev/null @@ -1,6 +0,0 @@ -import { makeStyles } from '@material-ui/core/styles'; -export default makeStyles(theme => ({ - appBar: { - position: 'relative' - } -})); diff --git a/src/components/Layout/MaterialLayout.jsx b/src/components/Layout/MaterialLayout.jsx index ffa0477..8a94bb2 100644 --- a/src/components/Layout/MaterialLayout.jsx +++ b/src/components/Layout/MaterialLayout.jsx @@ -1,22 +1,34 @@ import React from 'react'; -import { Paper, CssBaseline } from '@material-ui/core'; -import { ThemeProvider } from '@material-ui/core/styles'; +import { Paper, CssBaseline } from '@mui/material'; +import { ThemeProvider } from '@mui/material/styles'; import Header from '../Header'; import Footer from '../Footer'; -import { theme, useStyle } from './styles'; +import { theme } from './styles'; export default function MaterialLayout(props) { const { children } = props; - const classes = useStyle(); return (
-
- {children} +
+ + {children} +