Frontend Development.mdc

Guidelines for frontend development in Langflow, focusing on React/TypeScript UI components, build processes, and frontend testing.

Views0
PublishedJan 15, 2026

Loading actions...

5 minBeginnerpromptSingle file

Skill content

Main instructions and any bundled files for this skill.

markdown

1. Frontend Environment Setup

Prerequisites

  • Node.js: v22.12 LTS for JavaScript runtime
  • Package Manager: npm (v10.9) for dependency management
  • Development Tools: Vite for build tooling

Frontend Service

make frontend  # Start Vite dev server on port 3000

2. Frontend Structure

Directory Layout

src/frontend/src/
├── components/          # Reusable UI components
├── pages/              # Page-level components
├── icons/              # Component icons and lazy loading
├── stores/             # State management (Zustand)
├── types/              # TypeScript type definitions
├── utils/              # Utility functions
├── hooks/              # Custom React hooks
├── services/           # API service functions
└── assets/             # Static assets

Key Technologies

  • React 18 with TypeScript
  • Vite for build tooling and dev server
  • Tailwind CSS for styling
  • Zustand for state management
  • React Flow for flow graph visualization
  • Lucide React for icons

3. Frontend Code Quality

Formatting

make format_frontend  # Format TypeScript/JavaScript code

Linting

make lint  # Run ESLint and TypeScript checks

Testing

make tests_frontend  # Run frontend tests (requires additional setup)

Pre-commit Workflow

  1. Run make format_frontend
  2. Run make lint
  3. Test changes in browser
  4. Commit changes

4. State Management

Zustand Stores

// stores/myStore.ts
import { create } from 'zustand';

interface MyState {
  value: string;
  setValue: (value: string) => void;
}

export const useMyStore = create<MyState>((set) => ({
  value: '',
  setValue: (value) => set({ value }),
}));

Using Stores in Components

// components/MyComponent.tsx
import { useMyStore } from '@/stores/myStore';

export function MyComponent() {
  const { value, setValue } = useMyStore();

  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

5. API Integration

Service Functions

// services/api.ts
import { api } from '@/controllers/API';

export async function createFlow(flowData: FlowData) {
  const response = await api.post('/flows/', flowData);
  return response.data;
}

export async function getFlows() {
  const response = await api.get('/flows/');
  return response.data;
}

Error Handling

// hooks/useApi.ts
import { useState, useCallback } from 'react';

export function useApi<T>(apiFunction: (...args: any[]) => Promise<T>) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const execute = useCallback(async (...args: any[]) => {
    try {
      setLoading(true);
      setError(null);
      const result = await apiFunction(...args);
      return result;
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error');
      throw err;
    } finally {
      setLoading(false);
    }
  }, [apiFunction]);

  return { execute, loading, error };
}

6. React Flow Integration

Flow Graph Components

// components/FlowGraph.tsx
import ReactFlow, {
  Node,
  Edge,
  Controls,
  Background
} from 'reactflow';

interface FlowGraphProps {
  nodes: Node[];
  edges: Edge[];
  onNodesChange: (changes: NodeChange[]) => void;
  onEdgesChange: (changes: EdgeChange[]) => void;
}

export function FlowGraph({
  nodes,
  edges,
  onNodesChange,
  onEdgesChange
}: FlowGraphProps) {
  return (
    <div className="w-full h-full">
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        fitView
      >
        <Controls />
        <Background />
      </ReactFlow>
    </div>
  );
}

Custom Node Types

// components/nodes/ComponentNode.tsx
import { memo } from 'react';
import { Handle, Position } from 'reactflow';

interface ComponentNodeProps {
  data: {
    label: string;
    icon?: string;
  };
}

export const ComponentNode = memo(({ data }: ComponentNodeProps) => {
  return (
    <div className="px-4 py-2 shadow-md rounded-md bg-white border">
      <Handle type="target" position={Position.Top} />

      <div className="flex items-center">
        {data.icon && (
          <img src={data.icon} alt="" className="w-4 h-4 mr-2" />
        )}
        <span className="text-sm">{data.label}</span>
      </div>

      <Handle type="source" position={Position.Bottom} />
    </div>
  );
});

7. Styling with Tailwind

Component Styling

// components/Button.tsx
import { cn } from '@/utils/cn';

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
  onClick?: () => void;
}

export function Button({
  variant = 'primary',
  size = 'md',
  children,
  onClick
}: ButtonProps) {
  return (
    <button
      className={cn(
        'rounded-md font-medium transition-colors',
        {
          'bg-blue-600 hover:bg-blue-700 text-white': variant === 'primary',
          'bg-gray-200 hover:bg-gray-300 text-gray-900': variant === 'secondary',
          'px-2 py-1 text-sm': size === 'sm',
          'px-4 py-2 text-base': size === 'md',
          'px-6 py-3 text-lg': size === 'lg',
        }
      )}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

Dark Mode Support

// hooks/useDarkMode.ts
import { useDarkStore } from '@/stores/darkStore';

export function useDarkMode() {
  const { dark, setDark } = useDarkStore();

  const toggle = () => setDark(!dark);

  return { isDark: dark, toggle };
}

8. Frontend Testing

Component Testing

// __tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/Button';

describe('Button', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });

  it('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);

    fireEvent.click(screen.getByText('Click me'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

Integration Testing

// __tests__/FlowEditor.test.tsx
import { render, screen } from '@testing-library/react';
import { FlowEditor } from '@/pages/FlowEditor';

describe('FlowEditor', () => {
  it('loads flow data correctly', async () => {
    render(<FlowEditor flowId="test-flow-id" />);

    // Wait for flow to load
    await screen.findByText('Flow loaded');

    expect(screen.getByTestId('flow-canvas')).toBeInTheDocument();
  });
});

9. Build and Deployment

Development Build

make build_frontend  # Build frontend static files

Production Build

cd src/frontend
npm run build  # Creates dist/ directory

Build Integration

  • Frontend builds to src/frontend/dist/
  • Backend serves static files from this directory in production
  • make init includes frontend build step

Frontend Development Checklist

  • Frontend service running with make frontend

  • Changes hot-reload correctly in browser

  • State management uses Zustand stores

  • API calls use proper error handling

  • Components styled with Tailwind CSS

  • Dark mode support implemented where needed

  • Code formatted with make format_frontend

  • Linting passed with make lint

  • Changes tested in both light and dark mode

  • Components styled with Tailwind CSS

  • Dark mode support implemented where needed

  • Code formatted with make format_frontend

  • Changes tested in both light and dark mode

Share: