use a state for hotCrumbs with using ProcessBreadCrumb to avoid unnecessary renderings and network calls w/ burnettk
This commit is contained in:
parent
c00d810704
commit
c36e342d1e
|
@ -0,0 +1,146 @@
|
|||
// @ts-ignore
|
||||
import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||
import {
|
||||
HotCrumbItem,
|
||||
HotCrumbItemArray,
|
||||
HotCrumbItemObject,
|
||||
ProcessGroup,
|
||||
ProcessGroupLite,
|
||||
ProcessModel,
|
||||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
type OwnProps = {
|
||||
hotCrumbs?: HotCrumbItem[];
|
||||
};
|
||||
|
||||
// export default function ProcessBreadcrumb({ hotCrumbs }: OwnProps) {
|
||||
export const default ProcessBreadcrumb = React.memo(({ hotCrumbs }: OwnProps) => {
|
||||
const [processEntity, setProcessEntity] = useState<
|
||||
ProcessGroup | ProcessModel | null
|
||||
>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const explodeCrumbItemObject = (crumb: HotCrumbItem) => {
|
||||
if ('entityToExplode' in crumb) {
|
||||
const { entityToExplode, entityType } = crumb;
|
||||
if (entityType === 'process-model-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-models/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
onUnauthorized: () => {},
|
||||
});
|
||||
} else if (entityType === 'process-group-id') {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
entityToExplode as string
|
||||
)}`,
|
||||
successCallback: setProcessEntity,
|
||||
onUnauthorized: () => {},
|
||||
});
|
||||
} else {
|
||||
setProcessEntity(entityToExplode as any);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (hotCrumbs) {
|
||||
hotCrumbs.forEach(explodeCrumbItemObject);
|
||||
}
|
||||
}, [hotCrumbs]);
|
||||
|
||||
const checkPermissions = (crumb: HotCrumbItemObject) => {
|
||||
if (!crumb.checkPermission) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
processEntity &&
|
||||
'actions' in processEntity &&
|
||||
processEntity.actions &&
|
||||
'read' in processEntity.actions
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const hotCrumbElement = () => {
|
||||
if (hotCrumbs) {
|
||||
const leadingCrumbLinks = hotCrumbs.map(
|
||||
(crumb: HotCrumbItemArray | HotCrumbItemObject) => {
|
||||
if (
|
||||
'entityToExplode' in crumb &&
|
||||
processEntity &&
|
||||
processEntity.parent_groups &&
|
||||
checkPermissions(crumb)
|
||||
) {
|
||||
const breadcrumbs = processEntity.parent_groups.map(
|
||||
(parentGroup: ProcessGroupLite) => {
|
||||
const fullUrl = `/process-groups/${modifyProcessIdentifierForPathParam(
|
||||
parentGroup.id
|
||||
)}`;
|
||||
return (
|
||||
<BreadcrumbItem key={parentGroup.id} href={fullUrl}>
|
||||
{parentGroup.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (crumb.linkLastItem) {
|
||||
let apiBase = '/process-groups';
|
||||
let dataQaTag = '';
|
||||
if (crumb.entityType.startsWith('process-model')) {
|
||||
apiBase = '/process-models';
|
||||
dataQaTag = 'process-model-breadcrumb-link';
|
||||
}
|
||||
const fullUrl = `${apiBase}/${modifyProcessIdentifierForPathParam(
|
||||
processEntity.id
|
||||
)}`;
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem
|
||||
key={processEntity.id}
|
||||
href={fullUrl}
|
||||
data-qa={dataQaTag}
|
||||
>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
} else {
|
||||
breadcrumbs.push(
|
||||
<BreadcrumbItem key={processEntity.id} isCurrentPage>
|
||||
{processEntity.display_name}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return breadcrumbs;
|
||||
}
|
||||
if (Array.isArray(crumb)) {
|
||||
const valueLabel = crumb[0];
|
||||
const url = crumb[1];
|
||||
if (!url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem isCurrentPage key={valueLabel}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
if (url && valueLabel) {
|
||||
return (
|
||||
<BreadcrumbItem key={valueLabel} href={url}>
|
||||
{valueLabel}
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
return <Breadcrumb noTrailingSlash>{leadingCrumbLinks}</Breadcrumb>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return <Breadcrumb noTrailingSlash>{hotCrumbElement()}</Breadcrumb>;
|
||||
})
|
|
@ -12,6 +12,12 @@ import {
|
|||
} from '../interfaces';
|
||||
import HttpService from '../services/HttpService';
|
||||
|
||||
// it is recommend to use a state for hotCrumbs so ProcessBreadCrumb does not attmept
|
||||
// to re-render. This is because javascript cannot tell if an array or object has changed
|
||||
// but react states can. If we simply initialize a ProcessBreadCrumb when
|
||||
// the component that uses it renders, we may get a request to process model show
|
||||
// every time a state changes in the parent component (any state, not even a related state).
|
||||
// For an example of usage see TaskShow.
|
||||
type OwnProps = {
|
||||
hotCrumbs?: HotCrumbItem[];
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { Grid, Column, Button, ButtonSet, Loading } from '@carbon/react';
|
||||
|
@ -16,6 +16,7 @@ import {
|
|||
BasicTask,
|
||||
ErrorForDisplay,
|
||||
EventDefinition,
|
||||
HotCrumbItem,
|
||||
Task,
|
||||
} from '../interfaces';
|
||||
import CustomForm from '../components/CustomForm';
|
||||
|
@ -31,6 +32,14 @@ export default function TaskShow() {
|
|||
const [basicTask, setBasicTask] = useState<BasicTask | null>(null);
|
||||
const [taskWithTaskData, setTaskWithTaskData] = useState<Task | null>(null);
|
||||
|
||||
// put this in a state so ProcessBreadCrumb does not attempt to re-render
|
||||
// since javascript cannot tell if an array or object has changed
|
||||
// but react states can. If we simply initialize a ProcessBreadCrumb when
|
||||
// this parent component renders, we get a request to process model show
|
||||
// every time someone types a character into a user task form (because we change ANY
|
||||
// state. in this case, setTaskData).
|
||||
const [hotCrumbs, setHotCrumbs] = useState<HotCrumbItem[]>([]);
|
||||
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [formButtonsDisabled, setFormButtonsDisabled] = useState(false);
|
||||
|
@ -49,7 +58,8 @@ export default function TaskShow() {
|
|||
|
||||
// if a user can complete a task then the for-me page should
|
||||
// always work for them so use that since it will work in all cases
|
||||
const navigateToInterstitial = (myTask: BasicTask) => {
|
||||
const navigateToInterstitial = useCallback(
|
||||
(myTask: BasicTask) => {
|
||||
if (UserService.onlyGuestTaskCompletion()) {
|
||||
setGuestConfirmationText('Thank you!');
|
||||
} else {
|
||||
|
@ -59,10 +69,12 @@ export default function TaskShow() {
|
|||
)}/${myTask.process_instance_id}/interstitial`
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const processBasicTaskResult = (result: BasicTask) => {
|
||||
const processBasicTaskResult = useCallback(
|
||||
(result: BasicTask) => {
|
||||
setBasicTask(result);
|
||||
setPageTitle([result.name_for_display]);
|
||||
if (!result.can_complete) {
|
||||
|
@ -76,7 +88,28 @@ export default function TaskShow() {
|
|||
navigateToInterstitial(result);
|
||||
}
|
||||
}
|
||||
};
|
||||
const hotCrumbList: HotCrumbItem[] = [
|
||||
['Process Groups', '/process-groups'],
|
||||
{
|
||||
entityToExplode: result.process_model_identifier,
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
checkPermission: true,
|
||||
},
|
||||
[
|
||||
`Process Instance Id: ${result.process_instance_id}`,
|
||||
`/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
||||
result.process_model_identifier
|
||||
)}/${result.process_instance_id}`,
|
||||
],
|
||||
[`Task: ${result.name_for_display || result.id}`],
|
||||
];
|
||||
setHotCrumbs(hotCrumbList);
|
||||
},
|
||||
[navigateToInterstitial, navigate]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const processTaskWithDataResult = (result: Task) => {
|
||||
setTaskWithTaskData(result);
|
||||
|
||||
|
@ -102,7 +135,7 @@ export default function TaskShow() {
|
|||
});
|
||||
// FIXME: not sure what to do about addError. adding it to this array causes the page to endlessly reload
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [params]);
|
||||
}, [params, processBasicTaskResult]);
|
||||
|
||||
// Before we auto-saved form data, we remembered what data was in the form, and then created a synthetic submit event
|
||||
// in order to implement a "Save and close" button. That button no longer saves (since we have auto-save), but the crazy
|
||||
|
@ -422,26 +455,7 @@ export default function TaskShow() {
|
|||
!('allowGuest' in basicTask.extensions) ||
|
||||
basicTask.extensions.allowGuest !== 'true'
|
||||
) {
|
||||
pageElements.push(
|
||||
<ProcessBreadcrumb
|
||||
hotCrumbs={[
|
||||
['Process Groups', '/process-groups'],
|
||||
{
|
||||
entityToExplode: basicTask.process_model_identifier,
|
||||
entityType: 'process-model-id',
|
||||
linkLastItem: true,
|
||||
checkPermission: true,
|
||||
},
|
||||
[
|
||||
`Process Instance Id: ${basicTask.process_instance_id}`,
|
||||
`/process-instances/for-me/${modifyProcessIdentifierForPathParam(
|
||||
basicTask.process_model_identifier
|
||||
)}/${basicTask.process_instance_id}`,
|
||||
],
|
||||
[`Task: ${basicTask.name_for_display || basicTask.id}`],
|
||||
]}
|
||||
/>
|
||||
);
|
||||
pageElements.push(<ProcessBreadcrumb hotCrumbs={hotCrumbs} />);
|
||||
pageElements.push(
|
||||
<h3>
|
||||
Task: {basicTask.name_for_display} (
|
||||
|
|
Loading…
Reference in New Issue