references/complexity-patterns.md
3,292 tokens · o200k_base · 12,933 bytes
Source excerpt starting at line 1.# Complexity Reduction Patterns This document provides patterns for reducing cognitive complexity in Langflow React components. ## Understanding Complexity ### SonarJS Cognitive Complexity Langflow does not have automated complexity analysis tools. Assess complexity manually using SonarJS cognitive complexity rules: - **Total Complexity**: Sum of all functions' complexity in the file- **Max Complexity**: Highest single function complexity ### What Increases Complexity | Pattern | Complexity Impact ||---------|-------------------|| `if/else` | +1 per branch || Nested conditions | +1 per nesting level || `switch/case` | +1 per case || `for/while/do` | +1 per loop || `&&`/`||` chains | +1 per operator || Nested callbacks | +1 per nesting level || `try/catch` | +1 per catch || Ternary expressions | +1 per nesting | ## Pattern 1: Replace Conditionals with Lookup Tables **Before** (complexity: ~15): ```typescriptconst getParameterComponent = (field: InputFieldType) => { if (field.type === "str") { if (field.multiline) { return <TextAreaComponent value={field.value} /> } else if (field.password) { return <PasswordInput value={field.value} /> } else if (field.options?.length) { return <Dropdown options={field.options} value={field.value} /> } else { return <InputComponent value={field.value} /> } } if (field.type === "int") { return <IntComponent value={field.value} /> } if (field.type === "float") { return <FloatComponent value={field.value} /> } if (field.type === "bool") { return <ToggleComponent value={field.value} /> } if (field.type === "code") { return <CodeAreaComponent value={field.value} /> } return null}``` **After** (complexity: ~3): ```typescript// Define lookup table outside componentconst FIELD_TYPE_MAP: Record<string, FC<FieldProps>> = { int: IntComponent, float: FloatComponent, bool: ToggleComponent, code: CodeAreaComponent, dict: DictComponent, file: FileComponent,} const getStrVariant = (field: InputFieldType): FC<FieldProps> => { if (field.multiline) return TextAreaComponent if (field.password) return PasswordInput if (field.options?.length) return Dropdown return InputComponent} // Clean component logicconst getParameterComponent = (field: InputFieldType) => { const Component = field.type === "str" ? getStrVariant(field) : FIELD_TYPE_MAP[field.type] if (!Component) return null return <Component value={field.value} />}``` ## Pattern 2: Use Early Returns **Before** (complexity: ~10): ```typescriptconst handleNodeBuild = () => { if (isAuthenticated) { if (hasValidFlow) { if (!isBuilding) { if (allInputsConnected) { startBuild() } else { showMissingInputsError() } } else { showBuildInProgressWarning() } } else { showInvalidFlowError() } } else { showAuthError() }}``` **After** (complexity: ~4): ```typescriptconst handleNodeBuild = () => { if (!isAuthenticated) { showAuthError() return } if (!hasValidFlow) { showInvalidFlowError() return } if (isBuilding) { showBuildInProgressWarning() return } if (!allInputsConnected) { showMissingInputsError() return } startBuild()}``` ## Pattern 3: Extract Complex Conditions **Before** (complexity: high): ```typescriptconst canRunFlow = (() => { if (flow.is_component) { return false } if (!nodes.length) { return false } if (isBuilding) { return false } if (nodes.some((n) => n.data?.node?.error)) { return false } if ( edges.some( (e) => !e.sourceHandle || !e.targetHandle || (e.data?.isInvalid && e.data.isInvalid === true), ) ) { return false } return true})()``` **After** (complexity: lower): ```typescript// Extract to named functionsconst hasValidNodes = (nodes: Node[]) => { return nodes.length > 0 && !nodes.some((n) => n.data?.node?.error)} const hasValidEdges = (edges: Edge[]) => { return !edges.some( (e) => !e.sourceHandle || !e.targetHandle || e.data?.isInvalid, )} // Clean main logicconst canRunFlow = !flow.is_component && !isBuilding && hasValidNodes(nodes) && hasValidEdges(edges)``` ## Pattern 4: Replace Chained Ternaries **Before** (complexity: ~5): ```typescriptconst buildStatusIcon = buildStatus === BuildStatus.BUILT ? <CheckCircle className="text-green-500" /> : buildStatus === BuildStatus.BUILDING ? <Loader className="animate-spin text-blue-500" /> : buildStatus === BuildStatus.ERROR ? <XCircle className="text-red-500" /> : <Circle className="text-gray-400" />``` **After** (complexity: ~2): ```typescriptconst BUILD_STATUS_ICONS: Record<BuildStatus, ReactNode> = { [BuildStatus.BUILT]: <CheckCircle className="text-green-500" />, [BuildStatus.BUILDING]: <Loader className="animate-spin text-blue-500" />, [BuildStatus.ERROR]: <XCircle className="text-red-500" />, [BuildStatus.IDLE]: <Circle className="text-gray-400" />,} const buildStatusIcon = BUILD_STATUS_ICONS[buildStatus] ?? BUILD_STATUS_ICONS[BuildStatus.IDLE]``` ## Pattern 5: Flatten Nested Loops **Before** (complexity: high): ```typescriptconst getConnectedNodes = (flow: FlowType) => { const results: ConnectedNode[] = [] for (const node of flow.data.nodes) { if (node.data?.node?.template) { for (const [fieldName, field] of Object.entries(node.data.node.template)) { if (field.type === "str" && field.load_from_db) { for (const variable of globalVariables) { if (variable.name === field.value) { results.push({ nodeId: node.id, fieldName, variableName: variable.name, }) } } } } } } return results}``` **After** (complexity: lower): ```typescript// Use functional approachconst getConnectedNodes = (flow: FlowType) => { return flow.data.nodes .filter((node) => node.data?.node?.template) .flatMap((node) => Object.entries(node.data.node.template) .filter(([, field]) => field.type === "str" && field.load_from_db) .flatMap(([fieldName, field]) => globalVariables .filter((variable) => variable.name === field.value) .map((variable) => ({ nodeId: node.id, fieldName, variableName: variable.name, })), ), )}``` ## Pattern 6: Extract Event Handler Logic **Before** (complexity: high in component): ```typescriptconst FlowCanvas = () => { const handleNodeDrop = useCallback( (event: DragEvent) => { event.preventDefault() const nodeData = JSON.parse(event.dataTransfer.getData("application/json")) if (!nodeData || !nodeData.type) return const position = screenToFlowPosition({ x: event.clientX, y: event.clientY, }) // Validate the node type exists const nodeType = types[nodeData.type] if (!nodeType) { setErrorData({ title: "Invalid node type" }) return } // Check for duplicates if component if (nodeData.node?.is_component) { const existingComponent = nodes.find( (n) => n.data.node?.display_name === nodeData.node.display_name, ) if (existingComponent) { // 20 more lines of duplicate handling... } } // Create the new node const newNode = buildNodeFromData(nodeData, position) setNodes((prev) => [...prev, newNode]) // 20 more lines of post-drop logic... }, [nodes, types, screenToFlowPosition], ) return <div>...</div>}``` **After** (complexity: lower): ```typescript// Extract to hookconst useNodeDrop = (nodes: Node[], types: Record<string, any>) => { const validateNodeData = useCallback( (nodeData: any): boolean => { if (!nodeData?.type) return false if (!types[nodeData.type]) return false return true }, [types], ) const checkDuplicateComponent = useCallback( (nodeData: any): Node | undefined => { if (!nodeData.node?.is_component) return undefined return nodes.find( (n) => n.data.node?.display_name === nodeData.node.display_name, ) }, [nodes], ) return { validateNodeData, checkDuplicateComponent }} // Component becomes cleanerconst FlowCanvas = () => { const { validateNodeData, checkDuplicateComponent } = useNodeDrop(nodes, types) const handleNodeDrop = useCallback( (event: DragEvent) => { event.preventDefault() const nodeData = JSON.parse(event.dataTransfer.getData("application/json")) if (!validateNodeData(nodeData)) { setErrorData({ title: "Invalid node type" }) return } const duplicate = checkDuplicateComponent(nodeData) if (duplicate) { handleDuplicate(duplicate, nodeData) return } const position = screenToFlowPosition({ x: event.clientX, y: event.clientY, }) const newNode = buildNodeFromData(nodeData, position) setNodes((prev) => [...prev, newNode]) }, [validateNodeData, checkDuplicateComponent, screenToFlowPosition], ) return <div>...</div>}``` ## Pattern 7: Reduce Boolean Logic Complexity **Before** (complexity: ~8): ```typescriptconst isNodeDisabled = !isAuthenticated || flow.is_component || isBuilding || node.data?.node?.frozen || (node.data?.node?.error && node.data.node.error !== "") || (!hasConnectedInputs && node.type !== "genericNode") || (isLocked && !isSuperUser)``` **After** (complexity: ~3): ```typescript// Extract meaningful boolean functionsconst hasNodeError = (node: Node) => { return !!node.data?.node?.error && node.data.node.error !== ""} const isNodeAccessible = (node: Node, isLocked: boolean, isSuperUser: boolean) => { if (isLocked && !isSuperUser) return false if (node.data?.node?.frozen) return false return true} const canInteractWithNode = (node: Node) => { if (!isAuthenticated) return false if (flow.is_component) return false if (isBuilding) return false if (hasNodeError(node)) return false if (!isNodeAccessible(node, isLocked, isSuperUser)) return false if (!hasConnectedInputs && node.type !== "genericNode") return false return true} const isNodeDisabled = !canInteractWithNode(node)``` ## Pattern 8: Simplify useMemo/useCallback Dependencies **Before** (complexity: multiple recalculations): ```typescriptconst processedNodes = useMemo(() => { let result = nodes if (searchTerm) { result = result.filter( (n) => n.data?.node?.display_name?.toLowerCase().includes(searchTerm.toLowerCase()) || n.data?.type?.toLowerCase().includes(searchTerm.toLowerCase()), ) } if (filterByType) { result = result.filter((n) => n.data?.type === filterByType) } if (sortOrder === "name") { result = [...result].sort((a, b) => (a.data?.node?.display_name ?? "").localeCompare(b.data?.node?.display_name ?? ""), ) } else if (sortOrder === "type") { result = [...result].sort((a, b) => (a.data?.type ?? "").localeCompare(b.data?.type ?? ""), ) } return result}, [nodes, searchTerm, filterByType, sortOrder])``` **After** (complexity: separated concerns): ```typescript// Separate filter and sort utilitiesconst filterNodes = (nodes: Node[], searchTerm: string, filterByType?: string) => { let result = nodes if (searchTerm) { const term = searchTerm.toLowerCase() result = result.filter( (n) => n.data?.node?.display_name?.toLowerCase().includes(term) || n.data?.type?.toLowerCase().includes(term), ) } if (filterByType) { result = result.filter((n) => n.data?.type === filterByType) } return result} const SORT_COMPARATORS: Record<string, (a: Node, b: Node) => number> = { name: (a, b) => (a.data?.node?.display_name ?? "").localeCompare(b.data?.node?.display_name ?? ""), type: (a, b) => (a.data?.type ?? "").localeCompare(b.data?.type ?? ""),} const sortNodes = (nodes: Node[], sortOrder: string) => { const comparator = SORT_COMPARATORS[sortOrder] if (!comparator) return nodes return [...nodes].sort(comparator)} // Clean component usageconst filteredNodes = useMemo( () => filterNodes(nodes, searchTerm, filterByType), [nodes, searchTerm, filterByType],) const processedNodes = useMemo( () => sortNodes(filteredNodes, sortOrder), [filteredNodes, sortOrder],)``` ## Target Metrics After Refactoring | Metric | Target ||--------|--------|| Total Complexity | < 50 || Max Function Complexity | < 30 || Function Length | < 30 lines || Nesting Depth | <= 3 levels || Conditional Chains | <= 3 conditions |