React & Frontend Development Skill
When working on frontend code in packages/react-ui or packages/ui-components, follow these guidelines strictly.
1. Modular Components
- Break the design into independent files. Avoid large, single-file outputs.
- Each component should have one clear responsibility.
- Reusable, pure components live in
packages/ui-componentsand must have Storybook stories inpackages/ui-components/src/stories/. - Application-specific components live in
packages/react-ui. - Do not make breaking changes to existing component interfaces (props, names) without discussion.
2. Logic Isolation
- Move event handlers and business logic into custom hooks (
use-*.ts). - Follow existing convention: hooks go in
hooks/directories alongside features (e.g.,features/campaigns/hooks/use-campaign-charts.ts). - Keep component files focused on rendering; delegate logic to hooks.
- Extract complex derived state into hooks or utility functions.
3. Data Fetching — react-query v5
- Use
QueryKeysfrom@/app/constants/query-keys.ts— never hardcode query key strings. - Use
useQueryfor reads,useMutationfor writes. - Invalidate related queries after mutations using
queryClient.invalidateQueries. - Use the
enabledoption for conditional queries. - Keep query functions in dedicated API modules (e.g.,
campaigns-api.ts).
Example — existing pattern:
import { QueryKeys } from '@/app/constants/query-keys';
import { useQuery } from '@tanstack/react-query';
import { campaignsApi } from '../campaigns-api';
export function useCampaignCharts(campaignId: string) {
const { data: chartData } = useQuery({
queryKey: [QueryKeys.campaignCharts, campaignId],
queryFn: () => campaignsApi.getCharts(campaignId),
});
const weekData = useMemo(() => /* derive from chartData */, [chartData]);
return { chartData, weekData };
}
4. React Hooks & Performance
- Prefer extracted event handlers at component scope when they are passed to memoized children, reused across multiple elements, used in dependency arrays, or contain non-trivial logic.
- Inline JSX callbacks are acceptable for simple, local interactions when they keep the code clearer and are not causing avoidable re-renders in hot paths.
- Use
useMemofor expensive computations or derived state. - Use
useCallbackselectively for handlers where referential stability matters (for example, props to memoized children or values used in hook dependency arrays). - Place hooks and any memoized values/functions near the top of the component, after state declarations, following existing file conventions.
Prefer extraction when logic is non-trivial or stability matters:
// ✅ Simple inline callback — fine when the handler is trivial and local
<Button => setOpen(true)}>Open</Button>;
// ✅ Extracted handler — preferred when logic is non-trivial or passed to memoized children
const handleSubmit = useCallback(
(values: FormValues) => {
const sanitized = sanitizeInput(values);
onSubmit?.(sanitized);
},
[onSubmit],
);
return <Form />;
5. Anti-Patterns Checklist
❌ Inline arrow functions in JSX for non-trivial logic or props to memoized children (can cause avoidable re-renders)
❌ Inline object/array literals in JSX props (breaks reference equality)
❌ Complex logic inside render (hard to test, poor separation of concerns)
❌ Missing dependency arrays in useEffect/useCallback/useMemo
❌ Unnecessary state (derive from props when possible)
❌ Hardcoded query key strings (use QueryKeys constants)
✅ Extract and memoize callbacks with useCallback when referential stability matters
✅ Extract complex JSX logic into separate memoized functions or sub-components
✅ Derive state with useMemo instead of storing duplicated state
✅ Keep component functions focused — one clear responsibility per function
✅ Move business logic into custom hooks
6. Styling
- Use shadcn components as the foundation.
- Use
cnutility to group Tailwind classnames:
<div
className={cn(
'absolute bottom-[-20px] left-1/2 -translate-x-1/2',
'w-[118px] h-[24px] flex items-center justify-center',
'font-satoshi font-medium text-xs text-blueAccent-500',
'border border-solid border-blueAccent-500 rounded-[4px]',
'bg-background-800',
{
'pt-2': !someVar,
},
)}
>
{t('Sample output data')}
</div>
7. Dynamic Lists & Overflow
When a component renders a variable-length list (.map() over connections, options, templates, search results, etc.), you MUST verify overflow behavior:
- Constrain container height — lists must not push sibling content (buttons, footers, navigation) off-screen. Use a scroll wrapper with bounded height.
- Use existing scroll components — prefer the project's
ScrollArea(from@openops/components/ui) or a dedicated scroll wrapper over ad-hocoverflow-y-auto+max-hclasses. This keeps behavior consistent and responsive breakpoints centralized. - Test with realistic data volume — when adding mock/test data for lists, use enough items (20+) to trigger overflow, then verify the scroll behavior works.
- Check the full viewport — after adding scroll constraints, confirm that the component works on both tall (1080p+) and short (laptop, 700px height) viewports.
Red flags to catch:
- A
.map()rendering inside a flex/grid container with no height constraint overflow-cliporoverflow-hiddenon a parent that hides scrollable content- Fixed-height containers that truncate without scroll
- Buttons or footers pushed below the viewport fold
When in doubt: render 20+ items and visually inspect before considering the work done.
8. Code Quality Constraints
- SOLID, DRY, Clean Code — small functions, clear names, no dead code.
- Pattern consistency — read existing code in the target area before writing new code.
- Run build, test, lint, type-check iteratively:
npx nx test react-ui
npx nx test ui-components
npx nx lint react-ui
npx nx lint ui-components
9. Project-Specific Context
- React 18 with functional components
- Zustand for state management
- react-query v5 (
@tanstack/react-query) for data fetching - shadcn for UI components
- Axios via existing wrapper in
api.ts; useqspackage for query strings - Tests go in
tests/folders alongside code (Jest) - Perform browser testing with the available project tooling before finalizing UI changes