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 { import {
TaskDefinition, TaskDefinition,
TaskMetadata, TaskMetadata,
@ -14,7 +14,7 @@ interface TaskEditorProps {
onValidate: (taskId: string, isValid: boolean) => void; onValidate: (taskId: string, isValid: boolean) => void;
} }
export const TaskEditor: React.FC<TaskEditorProps> = ({ const TaskEditorComponent: React.FC<TaskEditorProps> = ({
task, task,
tasks, tasks,
tasksMetadata, tasksMetadata,
@ -41,23 +41,34 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
[onValidate], [onValidate],
); );
const tasksRef = React.useRef(tasks);
React.useEffect(() => { React.useEffect(() => {
runValidation(task, tasks, tasksMetadata); tasksRef.current = tasks;
}, [task.config.guid, runValidation]); }, [tasks]);
const handleTaskChange = (updatedTask: TaskDefinition) => { React.useEffect(() => {
// Update the task list runValidation(task, tasksRef.current, tasksMetadata);
const updatedTasks = tasks.map((t) => }, [task.config.guid, runValidation, tasksMetadata]);
t.config.guid === updatedTask.config.guid ? updatedTask : t,
);
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 runValidation(updatedTask, updatedTasks, tasksMetadata);
onChange(updatedTask);
};
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 ( return (
<div> <div>
@ -83,3 +94,22 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
</div> </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, { import templateVersionsService, {
CreateWorkflowTemplateVersion, CreateWorkflowTemplateVersion,
TaskDefinition, TaskDefinition,
@ -60,10 +60,31 @@ const TasksTab: React.FC<TasksTabProps> = ({
setSelectedTask(tasks[0]); setSelectedTask(tasks[0]);
}, [tasks, taskValidation, selectedTask]); }, [tasks, taskValidation, selectedTask]);
const handleTasksChange = (newTasks: TaskDefinition[]) => { const handleTasksChange = React.useCallback(
// Update the parent form state (newTasks: TaskDefinition[]) => {
onTasksChange("tasks", newTasks); // 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 ( return (
<div className="two-column-grid no-scroll"> <div className="two-column-grid no-scroll">
@ -84,20 +105,7 @@ const TasksTab: React.FC<TasksTabProps> = ({
tasksMetadata={tasksMetadata} tasksMetadata={tasksMetadata}
task={selectedTask} task={selectedTask}
tasks={tasks} tasks={tasks}
onChange={(updatedTask) => { onChange={handleTaskEditorChange}
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!);
}}
onValidate={onValidate} onValidate={onValidate}
/> />
</div> </div>