diff --git a/README.md b/README.md index 57679edd..0947f34f 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,6 @@ - Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳 - Improvements (and breaking changes in `import { useKcMessage } from "keycloakify"`. -**NOTICE**: [`powerhooks`](https://www.npmjs.com/package/powerhooks) have to be updated ([it's a peerDependency since v3](#v3)) - # Motivations Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications. diff --git a/package.json b/package.json index b51a35ea..2bf1662b 100755 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "homepage": "https://github.com/garronej/keycloakify", "peerDependencies": { "@emotion/react": "^11.4.1", - "powerhooks": "^0.11.0", + "powerhooks": "^0.10.0", "react": "^16.8.0 || ^17.0.0", "tss-react": "^1.1.0" }, diff --git a/src/lib/useFormValidationSlice.tsx b/src/lib/useFormValidationSlice.tsx index 8cd81f7a..af8ad984 100644 --- a/src/lib/useFormValidationSlice.tsx +++ b/src/lib/useFormValidationSlice.tsx @@ -5,7 +5,6 @@ import { useKcMessage } from "./i18n/useKcMessage"; import { useConstCallback } from "powerhooks/useConstCallback"; import { id } from "tsafe/id"; import type { MessageKey } from "./i18n/useKcMessage"; -import { useConst } from "powerhooks/useConst"; import { emailRegexp } from "./tools/emailRegExp"; export type KcContextLike = { @@ -278,6 +277,7 @@ export function useFormValidationSlice(params: { passwordRequired: boolean; realm: { registrationEmailAsUsername: boolean }; }; + /** NOTE: Try to avoid passing a new ref every render for better performances. */ passwordValidators?: Validators; }) { const { @@ -290,49 +290,51 @@ export function useFormValidationSlice(params: { }, } = params; - const attributesWithPassword = useConst(() => - !kcContext.passwordRequired - ? kcContext.profile.attributes - : (() => { - const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username"; + const attributesWithPassword = useMemo( + () => + !kcContext.passwordRequired + ? kcContext.profile.attributes + : (() => { + const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username"; - return kcContext.profile.attributes.reduce( - (prev, curr) => [ - ...prev, - ...(curr.name !== name - ? [curr] - : [ - curr, - id({ - "name": "password", - "displayName": id<`\${${MessageKey}}`>("${password}"), - "required": true, - "readOnly": false, - "validators": passwordValidators, - "annotations": {}, - "groupAnnotations": {}, - }), - id({ - "name": "password-confirm", - "displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"), - "required": true, - "readOnly": false, - "validators": { - "_compareToOther": { - "name": "password", - "ignore.empty.value": true, - "shouldBe": "equal", - "error-message": id<`\${${MessageKey}}`>("${invalidPasswordConfirmMessage}"), + return kcContext.profile.attributes.reduce( + (prev, curr) => [ + ...prev, + ...(curr.name !== name + ? [curr] + : [ + curr, + id({ + "name": "password", + "displayName": id<`\${${MessageKey}}`>("${password}"), + "required": true, + "readOnly": false, + "validators": passwordValidators, + "annotations": {}, + "groupAnnotations": {}, + }), + id({ + "name": "password-confirm", + "displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"), + "required": true, + "readOnly": false, + "validators": { + "_compareToOther": { + "name": "password", + "ignore.empty.value": true, + "shouldBe": "equal", + "error-message": id<`\${${MessageKey}}`>("${invalidPasswordConfirmMessage}"), + }, }, - }, - "annotations": {}, - "groupAnnotations": {}, - }), - ]), - ], - [], - ); - })(), + "annotations": {}, + "groupAnnotations": {}, + }), + ]), + ], + [], + ); + })(), + [kcContext, passwordValidators], ); const { getErrors } = useGetErrors({ @@ -344,27 +346,29 @@ export function useFormValidationSlice(params: { }, }); - const initialInternalState = useConst(() => - Object.fromEntries( - attributesWithPassword - .map(attribute => ({ - attribute, - "errors": getErrors({ - "name": attribute.name, - "fieldValueByAttributeName": Object.fromEntries( - attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }]), - ), - }), - })) - .map(({ attribute, errors }) => [ - attribute.name, - { - "value": attribute.value ?? "", - errors, - "doDisplayPotentialErrorMessages": errors.length !== 0, - }, - ]), - ), + const initialInternalState = useMemo( + () => + Object.fromEntries( + attributesWithPassword + .map(attribute => ({ + attribute, + "errors": getErrors({ + "name": attribute.name, + "fieldValueByAttributeName": Object.fromEntries( + attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }]), + ), + }), + })) + .map(({ attribute, errors }) => [ + attribute.name, + { + "value": attribute.value ?? "", + errors, + "doDisplayPotentialErrorMessages": errors.length !== 0, + }, + ]), + ), + [attributesWithPassword], ); type InternalState = typeof initialInternalState; @@ -421,7 +425,7 @@ export function useFormValidationSlice(params: { errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required), ), }), - [formValidationInternalState], + [formValidationInternalState, attributesWithPassword], ); return { formValidationState, formValidationReducer, attributesWithPassword };