fix accordion rebder bug from flippable component (#3203)

This commit is contained in:
Jonathan Dobson
2025-08-15 08:53:29 -04:00
committed by GitHub
parent 40d104c193
commit f2e33c69bc
10 changed files with 100 additions and 16 deletions

View File

@@ -0,0 +1,34 @@
import { useState } from "react";
/**
* ```tsx
* const { bump, key } = useRerender({ delay: 40,prefix: "my-prefix" });
*
* <div key={key}>...</div>
*
* // somewhere else
* bump();
* ```
*/
const useRerender = ({
delay = 40,
prefix,
}: {
delay?: number;
prefix: string;
}) => {
const [forceRenderKey, setForceRenderKey] = useState(`${prefix}-0`);
const bump = () => {
setTimeout(() => {
setForceRenderKey((prev) => `${prefix}-${prev + 1}`);
}, delay);
};
return {
bump,
key: forceRenderKey,
};
};
export { useRerender };

View File

@@ -25,6 +25,7 @@ import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { Switch } from "@/components/ui/switch";
import { placeholders, helpTooltips } from "../../helpContent";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { useRerender } from "@/hooks/useRerender";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
import { AppNode } from "..";
@@ -77,6 +78,7 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
const thisBlockIsPlaying =
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
const rerender = useRerender({ prefix: "accordian" });
const nodes = useNodes<AppNode>();
const edges = useEdges();
@@ -189,6 +191,7 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
"pointer-events-none opacity-50": thisBlockIsPlaying,
})}
type="single"
onValueChange={() => rerender.bump()}
collapsible
>
<AccordionItem value="advanced" className="border-b-0">
@@ -196,7 +199,7 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
Advanced Settings
</AccordionTrigger>
<AccordionContent className="pl-6 pr-1 pt-1">
<div className="space-y-4">
<div key={rerender.key} className="space-y-4">
<div className="space-y-2">
<ModelSelector
className="nopan w-52 text-xs"

View File

@@ -40,6 +40,7 @@ import { NodeHeader } from "../components/NodeHeader";
import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { useRerender } from "@/hooks/useRerender";
function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
const { updateNodeData } = useReactFlow();
@@ -70,6 +71,7 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
const nodes = useNodes<AppNode>();
const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
const rerender = useRerender({ prefix: "accordian" });
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
@@ -161,13 +163,17 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
}}
/>
<Separator />
<Accordion type="single" collapsible>
<Accordion
type="single"
collapsible
onValueChange={() => rerender.bump()}
>
<AccordionItem value="advanced" className="border-b-0">
<AccordionTrigger className="py-0">
Advanced Settings
</AccordionTrigger>
<AccordionContent className="pl-6 pr-1 pt-1">
<div className="space-y-4">
<div key={rerender.key} className="space-y-4">
<div className="space-y-2">
<ModelSelector
className="nopan w-52 text-xs"

View File

@@ -40,6 +40,7 @@ import { NodeHeader } from "../components/NodeHeader";
import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { useRerender } from "@/hooks/useRerender";
const urlTooltip =
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
@@ -77,6 +78,7 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
engine: data.engine,
model: data.model,
});
const rerender = useRerender({ prefix: "accordian" });
const nodes = useNodes<AppNode>();
const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
@@ -171,13 +173,17 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
</div>
</div>
<Separator />
<Accordion type="single" collapsible>
<Accordion
type="single"
collapsible
onValueChange={() => rerender.bump()}
>
<AccordionItem value="advanced" className="border-b-0">
<AccordionTrigger className="py-0">
Advanced Settings
</AccordionTrigger>
<AccordionContent className="pl-6 pr-1 pt-1">
<div className="space-y-4">
<div key={rerender.key} className="space-y-4">
<div className="space-y-2">
<ModelSelector
className="nopan w-52 text-xs"

View File

@@ -43,6 +43,7 @@ import { CodeIcon, PlusIcon, MagicWandIcon } from "@radix-ui/react-icons";
import { CurlImportDialog } from "./CurlImportDialog";
import { QuickHeadersDialog } from "./QuickHeadersDialog";
import { MethodBadge, UrlValidator, RequestPreview } from "./HttpUtils";
import { useRerender } from "@/hooks/useRerender";
const httpMethods = [
"GET",
@@ -83,6 +84,7 @@ function HttpRequestNode({ id, data }: NodeProps<HttpRequestNodeType>) {
});
const deleteNodeCallback = useDeleteNodeCallback();
const rerender = useRerender({ prefix: "accordian" });
const nodes = useNodes<AppNode>();
const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
@@ -319,12 +321,16 @@ function HttpRequestNode({ id, data }: NodeProps<HttpRequestNodeType>) {
<Separator />
<Accordion type="single" collapsible>
<Accordion
type="single"
collapsible
onValueChange={() => rerender.bump()}
>
<AccordionItem value="advanced" className="border-b-0">
<AccordionTrigger className="py-0">
Advanced Settings
</AccordionTrigger>
<AccordionContent className="pl-6 pr-1 pt-1">
<AccordionContent key={rerender.key} className="pl-6 pr-1 pt-1">
<div className="space-y-4">
<ParametersMultiSelect
availableOutputParameters={outputParameterKeys}

View File

@@ -41,6 +41,7 @@ import { NodeHeader } from "../components/NodeHeader";
import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { useRerender } from "@/hooks/useRerender";
function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
const { updateNodeData } = useReactFlow();
@@ -73,6 +74,7 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
model: data.model,
});
const rerender = useRerender({ prefix: "accordian" });
const nodes = useNodes<AppNode>();
const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
@@ -185,12 +187,16 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
</div>
</div>
<Separator />
<Accordion type="single" collapsible>
<Accordion
type="single"
collapsible
onValueChange={() => rerender.bump()}
>
<AccordionItem value="advanced" className="border-b-0">
<AccordionTrigger className="py-0">
Advanced Settings
</AccordionTrigger>
<AccordionContent className="pl-6 pr-1 pt-1">
<AccordionContent key={rerender.key} className="pl-6 pr-1 pt-1">
<div className="space-y-4">
<div className="space-y-2">
<ModelSelector

View File

@@ -14,6 +14,7 @@ import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { useRerender } from "@/hooks/useRerender";
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useBlockScriptStore } from "@/store/BlockScriptStore";
@@ -58,6 +59,7 @@ function NavigationNode({ id, data, type }: NodeProps<NavigationNode>) {
const thisBlockIsPlaying =
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
const rerender = useRerender({ prefix: "accordian" });
const [inputs, setInputs] = useState({
allowDownloads: data.allowDownloads,
cacheActions: data.cacheActions,
@@ -194,13 +196,14 @@ function NavigationNode({ id, data, type }: NodeProps<NavigationNode>) {
})}
type="single"
collapsible
onValueChange={() => rerender.bump()}
>
<AccordionItem value="advanced" className="border-b-0">
<AccordionTrigger className="py-0">
Advanced Settings
</AccordionTrigger>
<AccordionContent className="pl-6 pr-1 pt-1">
<div className="space-y-4">
<div key={rerender.key} className="space-y-4">
<div className="space-y-2">
<ParametersMultiSelect
availableOutputParameters={outputParameterKeys}

View File

@@ -42,6 +42,7 @@ import { NodeHeader } from "../components/NodeHeader";
import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { useRerender } from "@/hooks/useRerender";
function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
const { updateNodeData } = useReactFlow();
@@ -59,6 +60,8 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
urlBlockLabel !== undefined && urlBlockLabel === label;
const thisBlockIsPlaying =
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
const rerender = useRerender({ prefix: "accordian" });
const nodes = useNodes<AppNode>();
const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
@@ -130,10 +133,14 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
totpUrl={inputs.totpVerificationUrl}
type={type}
/>
<Accordion type="multiple" defaultValue={["content", "extraction"]}>
<Accordion
type="multiple"
defaultValue={["content", "extraction"]}
onValueChange={() => rerender.bump()}
>
<AccordionItem value="content">
<AccordionTrigger>Content</AccordionTrigger>
<AccordionContent className="pl-[1.5rem] pr-1">
<AccordionContent key={rerender.key} className="pl-[1.5rem] pr-1">
<div className="space-y-4">
<div className="space-y-2">
<div className="flex justify-between">

View File

@@ -21,6 +21,7 @@ import { NodeHeader } from "../components/NodeHeader";
import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { useRerender } from "@/hooks/useRerender";
function Taskv2Node({ id, data, type }: NodeProps<Taskv2Node>) {
const { debuggable, editable, label } = data;
@@ -36,6 +37,7 @@ function Taskv2Node({ id, data, type }: NodeProps<Taskv2Node>) {
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
const { updateNodeData } = useReactFlow();
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
const rerender = useRerender({ prefix: "accordian" });
const [inputs, setInputs] = useState({
prompt: data.prompt,
@@ -122,12 +124,16 @@ function Taskv2Node({ id, data, type }: NodeProps<Taskv2Node>) {
</div>
</div>
<Separator />
<Accordion type="single" collapsible>
<Accordion
type="single"
collapsible
onValueChange={() => rerender.bump()}
>
<AccordionItem value="advanced" className="border-b-0">
<AccordionTrigger className="py-0">
Advanced Settings
</AccordionTrigger>
<AccordionContent className="pl-6 pr-1 pt-4">
<AccordionContent key={rerender.key} className="pl-6 pr-1 pt-4">
<div className="space-y-4">
<ModelSelector
className="nopan w-52 text-xs"

View File

@@ -38,6 +38,7 @@ import { NodeHeader } from "../components/NodeHeader";
import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
import { useRerender } from "@/hooks/useRerender";
function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
const { updateNodeData } = useReactFlow();
@@ -61,6 +62,8 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
errorCodeMapping: data.errorCodeMapping,
model: data.model,
});
const rerender = useRerender({ prefix: "accordian" });
const nodes = useNodes<AppNode>();
const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
@@ -143,13 +146,17 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
/>
</div>
<Separator />
<Accordion type="single" collapsible>
<Accordion
type="single"
collapsible
onValueChange={() => rerender.bump()}
>
<AccordionItem value="advanced" className="border-b-0">
<AccordionTrigger className="py-0">
Advanced Settings
</AccordionTrigger>
<AccordionContent>
<div className="ml-6 mt-4 space-y-4">
<div key={rerender.key} className="ml-6 mt-4 space-y-4">
<div className="space-y-2">
<ModelSelector
className="nopan mr-[1px] w-52 text-xs"