Optimisations to try and lower the number of rerenders required when switching tasks

This commit is contained in:
Colin Dawson 2026-02-16 22:27:29 +00:00
parent f530fc7efa
commit bde4247e29
2 changed files with 71 additions and 33 deletions

View File

@ -1,4 +1,4 @@
import React, { useCallback } from "react";
import React, { useCallback, useMemo } from "react";
import {
TaskDefinition,
TaskMetadata,
@ -14,7 +14,7 @@ interface TaskEditorProps {
onValidate: (taskId: string, isValid: boolean) => void;
}
export const TaskEditor: React.FC<TaskEditorProps> = ({
const TaskEditorComponent: React.FC<TaskEditorProps> = ({
task,
tasks,
tasksMetadata,
@ -41,23 +41,34 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
[onValidate],
);
const tasksRef = React.useRef(tasks);
React.useEffect(() => {
runValidation(task, tasks, tasksMetadata);
}, [task.config.guid, runValidation]);
tasksRef.current = tasks;
}, [tasks]);
const handleTaskChange = (updatedTask: TaskDefinition) => {
// Update the task list
const updatedTasks = tasks.map((t) =>
t.config.guid === updatedTask.config.guid ? updatedTask : t,
);
React.useEffect(() => {
runValidation(task, tasksRef.current, tasksMetadata);
}, [task.config.guid, runValidation, tasksMetadata]);
runValidation(updatedTask, updatedTasks, tasksMetadata);
const handleTaskChange = useCallback(
(updatedTask: TaskDefinition) => {
// Update the task list
const updatedTasks = tasks.map((t) =>
t.config.guid === updatedTask.config.guid ? updatedTask : t,
);
// Bubble updated task up
onChange(updatedTask);
};
runValidation(updatedTask, updatedTasks, tasksMetadata);
const taskMeta = tasksMetadata.find((t) => t.taskType === task.type);
// Bubble updated task up
onChange(updatedTask);
},
[tasks, tasksMetadata, runValidation, onChange],
);
const taskMeta = useMemo(
() => tasksMetadata.find((t) => t.taskType === task.type),
[tasksMetadata, task.type],
);
return (
<div>
@ -83,3 +94,22 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
</div>
);
};
export const TaskEditor = React.memo(
TaskEditorComponent,
(prevProps, nextProps) => {
const taskIdChanged =
prevProps.task.config.guid !== nextProps.task.config.guid;
const metaChanged = prevProps.tasksMetadata !== nextProps.tasksMetadata;
const onChangeChanged = prevProps.onChange !== nextProps.onChange;
const onValidateChanged = prevProps.onValidate !== nextProps.onValidate;
// Skip tasks comparison - we use a ref to access it
return !(
taskIdChanged ||
metaChanged ||
onChangeChanged ||
onValidateChanged
);
},
);

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
import templateVersionsService, {
CreateWorkflowTemplateVersion,
TaskDefinition,
@ -60,10 +60,31 @@ const TasksTab: React.FC<TasksTabProps> = ({
setSelectedTask(tasks[0]);
}, [tasks, taskValidation, selectedTask]);
const handleTasksChange = (newTasks: TaskDefinition[]) => {
// Update the parent form state
onTasksChange("tasks", newTasks);
};
const handleTasksChange = React.useCallback(
(newTasks: TaskDefinition[]) => {
// Update the parent form state
onTasksChange("tasks", newTasks);
},
[onTasksChange],
);
const handleTaskEditorChange = React.useCallback(
(updatedTask: TaskDefinition) => {
const newTasks = tasks.map((t) =>
t.config.guid === updatedTask.config.guid ? updatedTask : t,
);
handleTasksChange(newTasks);
// Use the updated object from the array, not the raw updatedTask
const updatedFromArray = newTasks.find(
(t) => t.config.guid === updatedTask.config.guid,
);
setSelectedTask(updatedFromArray!);
},
[tasks, handleTasksChange],
);
return (
<div className="two-column-grid no-scroll">
@ -84,20 +105,7 @@ const TasksTab: React.FC<TasksTabProps> = ({
tasksMetadata={tasksMetadata}
task={selectedTask}
tasks={tasks}
onChange={(updatedTask) => {
const newTasks = tasks.map((t) =>
t.config.guid === updatedTask.config.guid ? updatedTask : t,
);
handleTasksChange(newTasks);
// Use the updated object from the array, not the raw updatedTask
const updatedFromArray = newTasks.find(
(t) => t.config.guid === updatedTask.config.guid,
);
setSelectedTask(updatedFromArray!);
}}
onChange={handleTaskEditorChange}
onValidate={onValidate}
/>
</div>