Skip to content

Commit b268b3a

Browse files
rhamiltoopenshift-cherrypick-robot
authored andcommitted
OCPBUGS-77415: Fix infinite recursion in project access form
1 parent 7c86c3d commit b268b3a

4 files changed

Lines changed: 36 additions & 19 deletions

File tree

frontend/packages/console-shared/src/hooks/formik-validation-fix.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@ export const useFormikValidationFix = (value: any) => {
1414
} else {
1515
validateForm();
1616
}
17-
}, [memoizedValue, validateForm]);
17+
// validateForm is a stable function from Formik context and doesn't need to be in deps.
18+
// Including it can cause infinite re-render loops when field arrays change.
19+
// eslint-disable-next-line react-hooks/exhaustive-deps
20+
}, [memoizedValue]);
1821
};

frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import { Content, ContentVariants } from '@patternfly/react-core';
3+
import type { FormikHelpers, FormikValues } from 'formik';
34
import { Formik } from 'formik';
4-
import * as _ from 'lodash';
55
import { useTranslation, Trans } from 'react-i18next';
66
import { Link } from 'react-router-dom-v5-compat';
77
import {
@@ -24,8 +24,10 @@ import {
2424
getRolesWithMultipleSubjects,
2525
getRolesToUpdate,
2626
} from './project-access-form-submit-utils';
27-
import { getUserRoleBindings, Roles } from './project-access-form-utils';
28-
import { Verb, UserRoleBinding } from './project-access-form-utils-types';
27+
import type { Roles } from './project-access-form-utils';
28+
import { getUserRoleBindings } from './project-access-form-utils';
29+
import type { UserRoleBinding } from './project-access-form-utils-types';
30+
import { Verb } from './project-access-form-utils-types';
2931
import { validationSchema } from './project-access-form-validation-utils';
3032
import ProjectAccessForm from './ProjectAccessForm';
3133

@@ -43,21 +45,31 @@ const ProjectAccess: React.FC<ProjectAccessProps> = ({
4345
fullFormView,
4446
}) => {
4547
const { t } = useTranslation();
46-
if ((!roleBindings.loaded && _.isEmpty(roleBindings.loadError)) || !roles.loaded) {
47-
return <LoadingBox />;
48-
}
4948

50-
const userRoleBindings: UserRoleBinding[] = getUserRoleBindings(
51-
roleBindings.data,
52-
Object.keys(roles.data),
53-
namespace,
49+
const userRoleBindings: UserRoleBinding[] = React.useMemo(
50+
() =>
51+
roleBindings?.loaded
52+
? getUserRoleBindings(roleBindings.data, Object.keys(roles.data), namespace)
53+
: [],
54+
[roleBindings, roles.data, namespace],
5455
);
5556

57+
const memoizedRoleBindings = React.useMemo(() => ({ projectAccess: userRoleBindings }), [
58+
userRoleBindings,
59+
]);
60+
5661
const rbacURL = getDocumentationURL(documentationURLs.usingRBAC);
5762

58-
const initialValues = {
59-
projectAccess: roleBindings.loaded && userRoleBindings,
60-
};
63+
const initialValues = React.useMemo(
64+
() => ({
65+
projectAccess: roleBindings?.loaded && userRoleBindings,
66+
}),
67+
[roleBindings?.loaded, userRoleBindings],
68+
);
69+
70+
if ((!roleBindings?.loaded && !roleBindings?.loadError) || !roles.loaded) {
71+
return <LoadingBox />;
72+
}
6173

6274
const handleSubmit = (values, actions) => {
6375
let newRoles = getNewRoles(initialValues.projectAccess, values.projectAccess);
@@ -110,8 +122,9 @@ const ProjectAccess: React.FC<ProjectAccessProps> = ({
110122
});
111123
};
112124

113-
const handleReset = (values, actions) => {
114-
actions.resetForm({ status: { success: null }, values: initialValues });
125+
const handleReset = (_values: FormikValues, actions: FormikHelpers<FormikValues>) => {
126+
actions.setStatus({ success: null });
127+
actions.setValues(initialValues);
115128
};
116129

117130
const projectAccessForm = (
@@ -156,7 +169,7 @@ const ProjectAccess: React.FC<ProjectAccessProps> = ({
156169
<ProjectAccessForm
157170
{...formikProps}
158171
roles={roles.data}
159-
roleBindings={initialValues}
172+
roleBindings={memoizedRoleBindings}
160173
onCancel={fullFormView ? history.goBack : null}
161174
/>
162175
)}

frontend/packages/dev-console/src/components/project-access/ProjectAccessForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const ProjectAccessForm: React.FC<ProjectAccessFormProps> = ({
5858
roles,
5959
roleBindings,
6060
values,
61+
initialValues,
6162
onCancel,
6263
}) => {
6364
const { t } = useTranslation();
@@ -67,7 +68,7 @@ const ProjectAccessForm: React.FC<ProjectAccessFormProps> = ({
6768
React.useEffect(() => {
6869
!_.isEqual(
6970
ignoreRoleBindingName(roleBindings.projectAccess),
70-
ignoreRoleBindingName(values.projectAccess),
71+
ignoreRoleBindingName(initialValues.projectAccess),
7172
)
7273
? setIsStaleInfo(true)
7374
: setIsStaleInfo(false);

frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ describe('Project Access', () => {
9797
roleBindings: {
9898
data: [],
9999
loaded: false,
100-
loadError: {},
100+
loadError: undefined,
101101
},
102102
roles: {
103103
data: defaultAccessRoles,

0 commit comments

Comments
 (0)