diff --git a/frontend/packages/console-shared/src/hooks/formik-validation-fix.ts b/frontend/packages/console-shared/src/hooks/formik-validation-fix.ts index bb426c18418..146da430b8e 100644 --- a/frontend/packages/console-shared/src/hooks/formik-validation-fix.ts +++ b/frontend/packages/console-shared/src/hooks/formik-validation-fix.ts @@ -14,5 +14,8 @@ export const useFormikValidationFix = (value: any) => { } else { validateForm(); } - }, [memoizedValue, validateForm]); + // validateForm is a stable function from Formik context and doesn't need to be in deps. + // Including it can cause infinite re-render loops when field arrays change. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [memoizedValue]); }; diff --git a/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx b/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx index 75a881db4a0..a51bbd3b2ef 100644 --- a/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx +++ b/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Content, ContentVariants } from '@patternfly/react-core'; +import type { FormikHelpers, FormikValues } from 'formik'; import { Formik } from 'formik'; -import * as _ from 'lodash'; import { useTranslation, Trans } from 'react-i18next'; import { Link } from 'react-router-dom-v5-compat'; import { @@ -24,8 +24,10 @@ import { getRolesWithMultipleSubjects, getRolesToUpdate, } from './project-access-form-submit-utils'; -import { getUserRoleBindings, Roles } from './project-access-form-utils'; -import { Verb, UserRoleBinding } from './project-access-form-utils-types'; +import type { Roles } from './project-access-form-utils'; +import { getUserRoleBindings } from './project-access-form-utils'; +import type { UserRoleBinding } from './project-access-form-utils-types'; +import { Verb } from './project-access-form-utils-types'; import { validationSchema } from './project-access-form-validation-utils'; import ProjectAccessForm from './ProjectAccessForm'; @@ -43,21 +45,31 @@ const ProjectAccess: React.FC = ({ fullFormView, }) => { const { t } = useTranslation(); - if ((!roleBindings.loaded && _.isEmpty(roleBindings.loadError)) || !roles.loaded) { - return ; - } - const userRoleBindings: UserRoleBinding[] = getUserRoleBindings( - roleBindings.data, - Object.keys(roles.data), - namespace, + const userRoleBindings: UserRoleBinding[] = React.useMemo( + () => + roleBindings?.loaded + ? getUserRoleBindings(roleBindings.data, Object.keys(roles.data), namespace) + : [], + [roleBindings, roles.data, namespace], ); + const memoizedRoleBindings = React.useMemo(() => ({ projectAccess: userRoleBindings }), [ + userRoleBindings, + ]); + const rbacURL = getDocumentationURL(documentationURLs.usingRBAC); - const initialValues = { - projectAccess: roleBindings.loaded && userRoleBindings, - }; + const initialValues = React.useMemo( + () => ({ + projectAccess: roleBindings?.loaded && userRoleBindings, + }), + [roleBindings?.loaded, userRoleBindings], + ); + + if ((!roleBindings?.loaded && !roleBindings?.loadError) || !roles.loaded) { + return ; + } const handleSubmit = (values, actions) => { let newRoles = getNewRoles(initialValues.projectAccess, values.projectAccess); @@ -110,8 +122,9 @@ const ProjectAccess: React.FC = ({ }); }; - const handleReset = (values, actions) => { - actions.resetForm({ status: { success: null }, values: initialValues }); + const handleReset = (_values: FormikValues, actions: FormikHelpers) => { + actions.setStatus({ success: null }); + actions.setValues(initialValues); }; const projectAccessForm = ( @@ -156,7 +169,7 @@ const ProjectAccess: React.FC = ({ )} diff --git a/frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx b/frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx index 0157edb2e85..d06cdfa70d0 100644 --- a/frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx +++ b/frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx @@ -58,6 +58,7 @@ const ProjectAccessForm: React.FC = ({ roles, roleBindings, values, + initialValues, onCancel, }) => { const { t } = useTranslation(); @@ -67,7 +68,7 @@ const ProjectAccessForm: React.FC = ({ React.useEffect(() => { !_.isEqual( ignoreRoleBindingName(roleBindings.projectAccess), - ignoreRoleBindingName(values.projectAccess), + ignoreRoleBindingName(initialValues.projectAccess), ) ? setIsStaleInfo(true) : setIsStaleInfo(false); diff --git a/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx b/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx index 82d19dfa113..fb319c8b04e 100644 --- a/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx +++ b/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx @@ -97,7 +97,7 @@ describe('Project Access', () => { roleBindings: { data: [], loaded: false, - loadError: {}, + loadError: undefined, }, roles: { data: defaultAccessRoles,