All skills
langflow-ai avatar

/component-refactoring

@3d1c122
by Langflowlangflow-ai/langflow155k stars
10,155

Refactor high-complexity React components in Langflow frontend. Use when manual complexity assessment shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.

Use this Skill: https://skilld.dev/gh/langflow-ai/langflow/component-refactoring

This session only. Nothing lands on disk.

referencescomplexity-patterns.md

≈3.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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
&&/`
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):

const 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):

// Define lookup table outside component
const 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 logic
const 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):

const handleNodeBuild = () => {
  if (isAuthenticated) {
    if (hasValidFlow) {
      if (!isBuilding) {
        if (allInputsConnected) {
          startBuild()
        } else {
          showMissingInputsError()
        }
      } else {
        showBuildInProgressWarning()
      }
    } else {
      showInvalidFlowError()
    }
  } else {
    showAuthError()
  }
}

After (complexity: ~4):

const 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):

const 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):

// Extract to named functions
const 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 logic
const canRunFlow =
  !flow.is_component &&
  !isBuilding &&
  hasValidNodes(nodes) &&
  hasValidEdges(edges)

Pattern 4: Replace Chained Ternaries

Before (complexity: ~5):

const 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):

const 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):

const 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):

// Use functional approach
const 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):

const 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):

// Extract to hook
const 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 cleaner
const 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):

const 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):

// Extract meaningful boolean functions
const 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):

const 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):

// Separate filter and sort utilities
const 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 usage
const 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

Source: SKILL.md on GitHub

No alerts17d4 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill provides technical guidance for refactoring React components. It includes instructions for executing local build and test scripts which could be targeted by indirect prompt injection if malicious code is present in the components being processed.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    1 finding · Score: 82/100

Signed by skilld at 3d1c122. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 16 hours ago.

Activeupdated 7 months ago

README badge

README badge for langflow-ai/langflow/component-refactoring