From f3ffe10c51d573e5fb87df958bbef949a4a08668 Mon Sep 17 00:00:00 2001 From: Gottfried Chen Date: Tue, 2 May 2023 11:57:28 +0200 Subject: [PATCH 1/4] switch formik to react-hook-form --- template.json | 3 +- .../components/auth/sites/AuthLoginSite.tsx | 127 +++++++++--------- .../src/components/ui/CustomInputField.tsx | 24 ++-- 3 files changed, 83 insertions(+), 71 deletions(-) diff --git a/template.json b/template.json index cf002ea..f03e819 100644 --- a/template.json +++ b/template.json @@ -8,19 +8,20 @@ "@emotion/styled": "^11.10.5", "@formatjs/intl-pluralrules": "5.1.4", "@formatjs/intl-relativetimeformat": "11.1.4", + "@hookform/resolvers": "^3.1.0", "@mui/icons-material": "^5.11.0", "@mui/lab": "^5.0.0-alpha.115", "@mui/material": "^5.11.4", "axios": "^1.3.4", "axios-auth-refresh": "^3.3.6", "dayjs": "^1.10.7", - "formik": "^2.2.9", "intl": "^1.2.5", "license-checker-webpack-plugin": "^0.2.1", "lodash": "^4.17.21", "query-string": "^7.0.1", "react": "18.2.0", "react-dom": "18.2.0", + "react-hook-form": "^7.43.9", "react-intl": "6.2.1", "react-query": "^3.39.3", "react-router": "^6.4.2", diff --git a/template/src/components/auth/sites/AuthLoginSite.tsx b/template/src/components/auth/sites/AuthLoginSite.tsx index 90256a6..d163a79 100644 --- a/template/src/components/auth/sites/AuthLoginSite.tsx +++ b/template/src/components/auth/sites/AuthLoginSite.tsx @@ -1,17 +1,18 @@ +import { yupResolver } from "@hookform/resolvers/yup"; import { Button } from "@mui/material"; -import { AxiosError } from "axios"; -import { Field, Form, Formik } from "formik"; import * as React from "react"; +import { Controller, useForm } from "react-hook-form"; import * as Yup from "yup"; import { t } from "../../../i18n/util"; import { useLogin } from "../../../network/api/useLogin"; import { useAuthStore } from "../../../stores/authStore"; import { useGeneralStore } from "../../../stores/generalStore"; import { usePushRoute } from "../../app/router/history"; -import { DashboardRoutes } from "../../dashboard/router/DashboardRoutes"; import { CustomInputField } from "../../ui/CustomInputField"; import { Colors } from "../../util/Colors"; import { ImageLogo } from "../../util/Images"; +import { DashboardRoutes } from "../../dashboard/router/DashboardRoutes"; +import { AxiosError } from "axios"; interface ILoginValues { email: string; @@ -19,6 +20,25 @@ interface ILoginValues { } export const AuthLoginSite = () => { + const { handleSubmit, formState, control } = useForm({ + defaultValues: { + email: "", + password: "", + }, + mode: "onBlur", + resolver: yupResolver( + Yup.object().shape({ + email: Yup.string() + .email(t("screen.login.form.email.validation_error")) + .required(t("screen.login.form.email.validation_error")) + .trim(), + password: Yup.string() + .min(6, t("screen.login.form.password.validation_error")) + .required(t("screen.login.form.password.validation_error")), + }), + ), + }); + const [error, setError] = React.useState(); const pushRoute = usePushRoute(); @@ -28,7 +48,7 @@ export const AuthLoginSite = () => { const setIsLoading = useGeneralStore((state) => state.setIsLoading); - const handleSubmit = async (model: ILoginValues) => { + const onSubmit = async (model: ILoginValues) => { setIsLoading(true); setError(""); @@ -84,64 +104,51 @@ export const AuthLoginSite = () => { > {t("screen.login.title")} -
- + ( + + )} + /> + + ( + + )} + /> + {error &&
{error}
} + - - )} -
-
+ {t("screen.login.form.submit")} + + ); diff --git a/template/src/components/ui/CustomInputField.tsx b/template/src/components/ui/CustomInputField.tsx index b15cacd..3391b59 100644 --- a/template/src/components/ui/CustomInputField.tsx +++ b/template/src/components/ui/CustomInputField.tsx @@ -1,12 +1,14 @@ import { MenuItem, TextField, TextFieldProps } from "@mui/material"; -import { FieldInputProps, FormikState, getIn } from "formik"; import * as React from "react"; +import { ControllerFieldState, ControllerRenderProps, FieldValues, UseFormStateReturn } from "react-hook-form"; import { FieldError } from "./FieldError"; type IProps = TextFieldProps & { - field: FieldInputProps; + field: ControllerRenderProps; + fieldState: ControllerFieldState; + formState: UseFormStateReturn; + onChange?: () => void; - form: FormikState; showValidationErrorText?: boolean; selectOptions?: { value: string; label: string }[]; }; @@ -20,16 +22,18 @@ export const CustomInputField = ({ minRows, maxRows, required, - form, + field, + fieldState, + formState, "aria-label": ariaLabel, placeholder, showValidationErrorText = true, selectOptions, onChange, }: IProps) => { - const fieldError = getIn(form.errors, field.name); - const showError = getIn(form.touched, field.name) && !!fieldError; + const fieldError = fieldState.error?.message; + const showError = fieldState.isTouched && !!fieldError; const handleChange = (event: React.ChangeEvent) => { field.onChange(event); @@ -42,10 +46,6 @@ export const CustomInputField = ({ {selectOptions?.map((selectOption) => ( From 19557373daf7d4f69754e66f4eed0846a7ae8899 Mon Sep 17 00:00:00 2001 From: Gottfried Chen Date: Tue, 2 May 2023 13:09:39 +0200 Subject: [PATCH 2/4] validate onTouched instead onBlur --- template/src/components/app/router/AppRouter.tsx | 2 +- template/src/components/auth/sites/AuthLoginSite.tsx | 2 +- template/src/components/ui/CustomInputField.tsx | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/template/src/components/app/router/AppRouter.tsx b/template/src/components/app/router/AppRouter.tsx index a121388..3fda927 100644 --- a/template/src/components/app/router/AppRouter.tsx +++ b/template/src/components/app/router/AppRouter.tsx @@ -1,6 +1,7 @@ import { ReactQueryDevtools } from "react-query/devtools"; import { BrowserRouter, Outlet, Route, Routes } from "react-router-dom"; import { BASE_NAME, DEBUG_PUBLIC_DASHBOARD, LOADING_INDICATOR_DELAY_MS } from "../../../config"; +import { useQueryParams } from "../../../hooks/useQueryParams"; import { IDebugTab, useDebugStore } from "../../../stores/debugStore"; import { useGeneralStore } from "../../../stores/generalStore"; import { AuthLoginSite } from "../../auth/sites/AuthLoginSite"; @@ -16,7 +17,6 @@ import { NoAuthOnlyRoute } from "./NoAuthOnlyRoute"; import { PrivateRoute } from "./PrivateRoute"; import { RoutingManager } from "./RoutingManager"; import ScrollToTop from "./ScrollToTop"; -import { useQueryParams } from "../../../hooks/useQueryParams"; export const AppRouter = () => { const isLoading = useGeneralStore((state) => state.isLoading); diff --git a/template/src/components/auth/sites/AuthLoginSite.tsx b/template/src/components/auth/sites/AuthLoginSite.tsx index d163a79..76c2441 100644 --- a/template/src/components/auth/sites/AuthLoginSite.tsx +++ b/template/src/components/auth/sites/AuthLoginSite.tsx @@ -25,7 +25,7 @@ export const AuthLoginSite = () => { email: "", password: "", }, - mode: "onBlur", + mode: "onTouched", resolver: yupResolver( Yup.object().shape({ email: Yup.string() diff --git a/template/src/components/ui/CustomInputField.tsx b/template/src/components/ui/CustomInputField.tsx index 3391b59..702c5d7 100644 --- a/template/src/components/ui/CustomInputField.tsx +++ b/template/src/components/ui/CustomInputField.tsx @@ -33,7 +33,6 @@ export const CustomInputField = ({ onChange, }: IProps) => { const fieldError = fieldState.error?.message; - const showError = fieldState.isTouched && !!fieldError; const handleChange = (event: React.ChangeEvent) => { field.onChange(event); @@ -49,7 +48,7 @@ export const CustomInputField = ({ fullWidth type={type} autoComplete={autoComplete} - error={showError} + error={!!fieldError} margin="dense" aria-label={ariaLabel} variant="outlined" @@ -71,7 +70,7 @@ export const CustomInputField = ({ ))} - {showValidationErrorText && {showError ? fieldError : ""}} + {showValidationErrorText && {fieldError}} ); }; From 0f5e5ace4d996837ec3d102d813ab77801cb78c8 Mon Sep 17 00:00:00 2001 From: Gottfried Chen Date: Tue, 2 May 2023 13:36:40 +0200 Subject: [PATCH 3/4] improved custom input --- .../components/auth/sites/AuthLoginSite.tsx | 33 +++++++------------ .../src/components/ui/CustomInputField.tsx | 15 ++++----- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/template/src/components/auth/sites/AuthLoginSite.tsx b/template/src/components/auth/sites/AuthLoginSite.tsx index 76c2441..e1b80c1 100644 --- a/template/src/components/auth/sites/AuthLoginSite.tsx +++ b/template/src/components/auth/sites/AuthLoginSite.tsx @@ -1,18 +1,18 @@ import { yupResolver } from "@hookform/resolvers/yup"; import { Button } from "@mui/material"; +import { AxiosError } from "axios"; import * as React from "react"; -import { Controller, useForm } from "react-hook-form"; +import { useForm } from "react-hook-form"; import * as Yup from "yup"; import { t } from "../../../i18n/util"; import { useLogin } from "../../../network/api/useLogin"; import { useAuthStore } from "../../../stores/authStore"; import { useGeneralStore } from "../../../stores/generalStore"; import { usePushRoute } from "../../app/router/history"; +import { DashboardRoutes } from "../../dashboard/router/DashboardRoutes"; import { CustomInputField } from "../../ui/CustomInputField"; import { Colors } from "../../util/Colors"; import { ImageLogo } from "../../util/Images"; -import { DashboardRoutes } from "../../dashboard/router/DashboardRoutes"; -import { AxiosError } from "axios"; interface ILoginValues { email: string; @@ -109,31 +109,22 @@ export const AuthLoginSite = () => { onSubmit={handleSubmit(onSubmit)} style={{ padding: 24, border: `1px solid ${Colors.PRIMARY_COLOR}`, borderTop: "none" }} > - ( - - )} + label={t("screen.login.form.email.label")} + type="email" + autoComplete="username" /> - ( - - )} + label={t("screen.login.form.password.label")} + type="password" + autoComplete="current-password" /> + {error &&
{error}
}