frontend-code-review

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

Review frontend code (.tsx, .ts, .js files) for quality, performance, and correctness against Langflow's frontend conventions. Supports pending-change reviews and file-targeted reviews.

.agents/skills/frontend-code-review/SKILL.md

Download bundle ↓
main · 595cd724 bundle filesScanned 2026-09-15

references/performance.md

1,969 tokens · o200k_base · 8,595 bytes

Source excerpt starting at line 1.
# Rule Catalog -- Performance Update this file when adding, editing, or removing Performance rules so the catalog remains accurate. ## @xyflow/react data usage IsUrgent: TrueCategory: Performance ### Description When working with the flow graph powered by @xyflow/react v12, use `useNodes()` and `useEdges()` hooks for rendering UI that displays node/edge data. For callbacks that mutate or read node/edge state imperatively (e.g., inside event handlers), use `useReactFlow()` or `useStoreApi()` to access the store directly. Avoid pulling the full node/edge arrays in callbacks, as this creates unnecessary subscriptions and re-renders. ### Suggested Fix ```tsx// Wrong -- subscribes to all node changes in a callback-only contextfunction MyPanel() {  const nodes = useNodes();  const handleExport = () => {    const data = nodes.map((n) => n.data);    exportFlow(data);  };  return <button onClick={handleExport}>Export</button>;} // Right -- use store API for imperative accessfunction MyPanel() {  const { getNodes } = useReactFlow();  const handleExport = () => {    const data = getNodes().map((n) => n.data);    exportFlow(data);  };  return <button onClick={handleExport}>Export</button>;}``` ## Complex prop memoization with `useMemo` IsUrgent: TrueCategory: Performance ### Description Wrap complex prop values (objects, arrays, computed structures) in `useMemo` before passing them to child components. Without memoization, a new reference is created on every render, causing children wrapped in `React.memo` to re-render unnecessarily. This is especially critical in the flow canvas where hundreds of nodes may be rendered simultaneously. ### Suggested Fix ```tsx// Wrong -- new object on every render<GenericNode  config={{    template: data.node.template,    display_name: data.node.display_name,  }}/> // Right -- stable referenceconst config = useMemo(  () => ({    template: data.node.template,    display_name: data.node.display_name,  }),  [data.node.template, data.node.display_name],); <GenericNode config={config} />``` ## Avoid re-renders with `React.memo` and `useCallback` IsUrgent: TrueCategory: Performance ### Description Use `React.memo` for components that receive stable props and should not re-render when their parent re-renders. Use `useCallback` for callback functions passed as props to memoized children. This is critical for node components on the canvas: GenericNode and its sub-components should be memoized to avoid cascading re-renders when the flow state changes. ### Suggested Fix ```tsx// Wrong -- inline callback causes child re-renderfunction ParentNode({ id }: { id: string }) {  return (    <ChildComponent      onUpdate={(value: string) => updateNodeField(id, value)}    />  );} // Right -- stable callback referencefunction ParentNode({ id }: { id: string }) {  const handleUpdate = useCallback(    (value: string) => updateNodeField(id, value),    [id],  );  return <ChildComponent onUpdate={handleUpdate} />;} const ChildComponent = React.memo(({ onUpdate }: Props) => {  // ...});``` ## Lazy loading for heavy pages IsUrgent: FalseCategory: Performance ### Description Use `React.lazy()` with `<Suspense>` for route-level code splitting of heavy pages (flow editor, settings, store, etc.). This reduces the initial bundle size and improves first-load performance. Route definitions should use lazy imports rather than static imports for page components. ### Suggested Fix ```tsx// Wrong -- static import of heavy pageimport FlowEditor from "@/pages/FlowEditor"; // Right -- lazy loadedconst FlowEditor = React.lazy(() => import("@/pages/FlowEditor")); <Suspense fallback={<LoadingSpinner />}>  <FlowEditor /></Suspense>``` ## No expensive operations in render functions IsUrgent: TrueCategory: Performance ### Description Do not perform expensive computations (deep object traversals, array sorting/filtering of large datasets, JSON parsing, regex on large strings) directly in the render body of a component. Move these into `useMemo` with appropriate dependencies, or into event handlers / effects if they produce side effects. The flow canvas may trigger frequent re-renders; keeping the render path lightweight is critical. ### Suggested Fix ```tsx// Wrong -- sorts on every renderfunction NodeList({ nodes }: { nodes: NodeType[] }) {  const sorted = nodes    .filter((n) => n.data.showNode)    .sort((a, b) => a.data.node.display_name.localeCompare(b.data.node.display_name));   return <>{sorted.map((n) => <NodeCard key={n.id} node={n} />)}</>;} // Right -- memoized computationfunction NodeList({ nodes }: { nodes: NodeType[] }) {  const sorted = useMemo(    () =>      nodes        .filter((n) => n.data.showNode)        .sort((a, b) =>          a.data.node.display_name.localeCompare(b.data.node.display_name),        ),    [nodes],  );   return <>{sorted.map((n) => <NodeCard key={n.id} node={n} />)}</>;}``` ## Zustand selector optimization IsUrgent: TrueCategory: Performance ### Description When reading from Zustand stores, always use selectors to subscribe to only the specific slices of state the component needs. Selecting the entire store object causes the component to re-render on every store update, even for unrelated state changes. Langflow has 15+ Zustand stores; careless subscriptions can create cascading performance problems. ### Suggested Fix ```ts// Wrong -- subscribes to entire store, re-renders on any changeconst store = useFlowStore();const nodes = store.nodes; // Wrong -- inline object selector creates new reference each renderconst { nodes, edges } = useFlowStore((state) => ({  nodes: state.nodes,  edges: state.edges,})); // Right -- individual selectors for each valueconst nodes = useFlowStore((state) => state.nodes);const edges = useFlowStore((state) => state.edges); // Right -- use useShallow for multiple values if neededimport { useShallow } from "zustand/react/shallow"; const { nodes, edges } = useFlowStore(  useShallow((state) => ({ nodes: state.nodes, edges: state.edges })),);``` ## React Query: proper staleTime and gcTime configuration IsUrgent: FalseCategory: Performance ### Description Configure `staleTime` and `gcTime` (formerly `cacheTime`) appropriately for each query based on how frequently the data changes. Avoid leaving defaults (0ms staleTime) for data that rarely changes (e.g., component type definitions, user settings), as this causes unnecessary refetches. Conversely, do not set excessively long stale times for data that changes frequently (e.g., flow execution status). ### Suggested Fix ```ts// Wrong -- default staleTime of 0, refetches on every mountconst { data } = useQuery({  queryKey: ["componentTypes"],  queryFn: getComponentTypes,}); // Right -- component types rarely change, cache for 5 minutesconst { data } = useQuery({  queryKey: ["componentTypes"],  queryFn: getComponentTypes,  staleTime: 5 * 60 * 1000,});``` ## Avoid creating new objects/arrays inline in JSX IsUrgent: FalseCategory: Performance ### Description Do not create new object or array literals directly inside JSX props, especially for configuration objects, data arrays, or callback payloads. Each render creates a new reference, which defeats `React.memo` and `useMemo` optimizations in child components. Extract these to constants outside the component, or memoize them inside the component. **Note**: Since this project uses Tailwind CSS, inline `style={}` props should be extremely rare. Use Tailwind classes with design tokens instead (see code-quality.md). This rule applies primarily to non-style object props like `config`, `data`, `options`, `columns`, etc. ### Suggested Fix ```tsx// Wrong -- new config object every render, child re-renders even if data hasn't changedfunction FlowSettings({ flowId }: { flowId: string }) {  return (    <SettingsPanel      config={{ flowId, autoSave: true, showMinimap: false }}    />  );} // Right -- constant outside component for static valuesconst DEFAULT_SETTINGS = { autoSave: true, showMinimap: false } as const; function FlowSettings({ flowId }: { flowId: string }) {  const config = useMemo(    () => ({ flowId, ...DEFAULT_SETTINGS }),    [flowId],  );  return <SettingsPanel config={config} />;} // Wrong -- new array every render<SelectComponent options={["option1", "option2", "option3"]} /> // Right -- constant outside componentconst OPTIONS = ["option1", "option2", "option3"] as const;<SelectComponent options={OPTIONS} />``` 
Referenced from SKILL.md