Search code examples
reactjsformikformik-material-ui

How to use Input Sanitization with Formik?


I am using useFormik hook to use Formik with my forms. In the configuration I have set validate, onSubmit and initialValues keys. I want to sanitize my inputs like trimming and escaping. For email I also want to use normalizeEmail from validator.js. I am not able to find where to put the sanitization code - do I put it in the formik config or in validate function. I realize that I can use setFieldValue from formik, but I don't want to change the formik.handleChange functionality as it handles the form states and don't want to screw that up.

Here is the code:

import { useEffect, useRef } from "react";
import { useSelector, useDispatch } from "react-redux";
import { NavLink as RouterLink, useHistory, useLocation } from "react-router-dom";
import Helmet from "react-helmet";
import {
    Button,
    LinearProgress,
    Typography,
    Paper,
    Grid,
    Box,
    TextField
} from "@material-ui/core";
import useMediaQuery from "@material-ui/core/useMediaQuery";
import { useFormik } from "formik";
import { useSnackbar } from "notistack";
import CenterLayout from "../CenterLayout";
import { logInUser } from "../../features/user/userSlice";
import { useTheme } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next";
import isEmail from 'validator/es/lib/isEmail';

const Login = () => {
    const { isLoadingLogin, errorLoginCode } = useSelector((state) => state.user);
    const prevIsLoadingLoginRef = useRef();
    const dispatch = useDispatch();
    const history = useHistory();
    const location = useLocation();
    const theme = useTheme();
    const isXS = useMediaQuery(theme.breakpoints.down("xs"));
    const { enqueueSnackbar } = useSnackbar();
    const { t } = useTranslation();

    const formik = useFormik({
        initialValues: {
            email: "",
            password: "",
        },
        onSubmit: (values) => {
            dispatch(logInUser(values));
        },
        validate: (values) => {
            const errors = {};
            if (!values.email) {
                errors.email = t('form.required');
            } else if (
                !isEmail(values.email)
            ) {
                errors.email = t('loginPage.invalidEmail');
            }

            if (!values.password) {
                errors.password = t('form.required');
            }
            return errors;
        },
    });

    useEffect(() => {
        if (prevIsLoadingLoginRef.current && !isLoadingLogin) {
            if (errorLoginCode.length === 0) {
                formik.resetForm();
                enqueueSnackbar(t("loginPage.loginSuccess"), {
                    variant: "success",
                });
                const { from } = location.state || { from: { pathname: "/dashboard" } };
                history.replace(from);
            } else {
                enqueueSnackbar(
                    `${t("loginPage.loginFailed")}: ${t("api." + errorLoginCode)}`,
                    { variant: "error" }
                );
            }
        }

        prevIsLoadingLoginRef.current = isLoadingLogin;
    }, [
        isLoadingLogin,
        errorLoginCode,
        history,
        location,
        formik,
        enqueueSnackbar,
        t,
    ]);

    console.log("dbg1", formik);


    return (
        <CenterLayout>
            <Helmet title="Circuittutor - Login" />
            <Grid item xs={12} sm={8} md={4}>
                <Paper elevation={isXS ? 0 : 1}>
                    <Box p={2}>
                        <Typography
                            variant="h6"
                            align="center"
                            color="secondary"
                            gutterBottom
                        >
                            {t("loginPage.form")}
                        </Typography>
                        <form onSubmit={formik.handleSubmit}>
                            <TextField
                                name="email"
                                type="email"
                                label={t("loginPage.email")}
                                fullWidth
                                style={{ marginBottom: theme.spacing(2) }}
                                value={formik.values.email}
                                onChange={formik.handleChange}
                                onBlur={formik.handleBlur}
                                error={
                                    formik.touched.email &&
                                    Boolean(formik.errors.email)
                                }
                                helperText={
                                    formik.touched.email && formik.errors.email
                                }
                            />

                            <TextField
                                name="password"
                                type="password"
                                label={t("loginPage.password")}
                                fullWidth
                                style={{ marginBottom: theme.spacing(2) }}
                                value={formik.values.password}
                                onChange={formik.handleChange}
                                onBlur={formik.handleBlur}
                                error={
                                    formik.touched.password &&
                                    Boolean(formik.errors.password)
                                }
                                helperText={
                                    formik.touched.password && formik.errors.password
                                }
                            />

                            {isLoadingLogin && (
                                <LinearProgress style={{ marginBottom: theme.spacing(2) }} />
                            )}

                            <Button
                                variant="contained"
                                color="secondary"
                                disabled={isLoadingLogin}
                                type="submit"
                                fullWidth={isXS}
                            >
                                {t("form.submit")}
                            </Button>
                        </form>
                        <Button
                            exact
                            variant="text"
                            disableElevation
                            color="primary"
                            component={RouterLink}
                            to="/forgotPassword"
                            style={{ marginTop: theme.spacing(2), padding: 0 }}
                        >
                            {t('loginPage.forgotPasswordBtn')}
                        </Button>
                    </Box>
                </Paper>
            </Grid>
        </CenterLayout>
    );
};

export default Login;

Solution

  • All you need to do is to intercept onChange event on you input field, do your sanitization logic and then call the setFieldValue helper with new computed value. You don't need to worry about anythin related to Formik internals - it's official way to provide custom input handlers