AI
50 AI Prompts for React and TypeScript Development
A developer-tested set of 50 prompts for building React applications with TypeScript — covering component patterns, custom hooks, forms, data fetching with React Query, context, performance, and testing with Vitest.
Advertisement
Why React + TypeScript Prompts Need to Be Different
Generic React prompts produce class-level boilerplate that looks correct but misses the TypeScript-specific patterns that actually matter in a real codebase. These prompts are written to get typed output from the start — properly typed props, event handlers, ref types, and hook return values — so you spend less time retrofitting types onto JavaScript-flavored code.
The prompts assume React 18, TypeScript 5, and Vite as the build tool. For data fetching they use React Query v5 (TanStack Query). For forms, React Hook Form with Zod resolvers. These are the combinations that work well together and that most mid-sized React projects land on after trying alternatives.
One thing worth noting: for component prompts, always describe the props interface you want before asking the AI to build the component. If you say "create a Modal component" you get generic output. If you say "create a Modal component with isOpen, onClose, title, and children props where isOpen controls visibility via a CSS transition" you get something closer to what you actually need.
- Describe the props interface before asking for the component — saves a rewrite
- For hook prompts, specify the return type shape alongside the input parameters
- React Query prompts work best when you paste your existing queryClient setup
- Test prompts need your component file pasted in — do not describe the component, show it
Component Architecture and TypeScript Basics (Prompts 1–8)
Prompt 1: "Create a typed Button component in React with TypeScript. Props: variant (primary | secondary | ghost | danger), size (sm | md | lg), isLoading (boolean, optional), disabled (boolean, optional), onClick, and all standard HTML button attributes via React.ButtonHTMLAttributes<HTMLButtonElement>. When isLoading is true, show a spinner and disable the button. Use Tailwind for styling with a variant map object. Export the ButtonProps type." — Prompt 2: "Build a typed Input component. Props: label (string), error (string, optional), helper (string, optional), and all standard HTML input attributes. When error is set, apply a red border and show the error below the input. When helper is set and there is no error, show the helper text in gray. Forward the ref correctly using React.forwardRef so the parent can focus the input programmatically."
Prompt 3: "Create a generic Table<T> component. Props: data (T[]), columns (array of { key: keyof T, header: string, render?: (value: T[key], row: T) => React.ReactNode }), isLoading (boolean), and emptyMessage (string). When isLoading is true, show skeleton rows. When data is empty, show emptyMessage centered. The render function in columns lets callers customize cell display for any column without losing type safety." — Prompt 4: "Write a Modal component. Props: isOpen, onClose, title, children, size (sm | md | lg | full), and hideCloseButton (optional boolean). Use a React Portal to render outside the component tree. Animate open and close with a CSS transition on the overlay opacity and panel translateY. Trap focus inside the modal when open. Close on Escape key press and on overlay click."
Prompt 5: "Create a Pagination component. Props: currentPage (number), totalPages (number), onPageChange (number) => void, and siblingCount (optional, default 1). Render page number buttons, previous and next arrows, and ellipsis indicators when there are many pages. Disable previous on page 1 and next on the last page. Show at most siblingCount pages on each side of the current page." — Prompt 6: "Build a Dropdown component with typed options. Props: options (Array<{ label: string, value: T }>), value (T | null), onChange ((value: T) => void), placeholder (string), and disabled (optional). Close the dropdown on outside click using a useOnClickOutside hook. Keyboard-navigable: arrow keys move selection, Enter confirms, Escape closes." — Prompt 7: "Create a Toast notification system. Build a useToast hook that provides showToast(message: string, type: success | error | info | warning, duration?: number) and a ToastContainer component that renders active toasts. Toasts slide in from the top right, auto-dismiss after the duration, and can be dismissed manually. Support up to 5 toasts stacked at once — older ones drop off when the limit is reached." — Prompt 8: "Write a FileUpload component. Props: accept (string), maxSizeMB (number), onUpload ((file: File) => Promise<void>), and multiple (boolean). Implement drag-and-drop with visual feedback. Validate file type against accept and reject files exceeding maxSizeMB with an inline error message. Show an upload progress indicator during the onUpload callback."
Custom Hooks (Prompts 9–16)
Prompt 9: "Write a useLocalStorage<T>(key: string, initialValue: T) hook. It should read the initial value from localStorage on mount (with a fallback to initialValue if the key does not exist or the stored value is invalid JSON). Return [value, setValue, removeValue] where setValue updates both state and localStorage. Handle SSR by checking for window before accessing localStorage." — Prompt 10: "Create a useDebounce<T>(value: T, delay: number) hook that delays propagating a value change until the delay has passed without a new change. Show how to use it to debounce a search input that triggers an API call — the API should only be called when the user stops typing for 400ms."
Prompt 11: "Write a useOnClickOutside(ref: RefObject<HTMLElement>, handler: () => void) hook that calls handler when a click event fires outside the referenced element. Use a mousedown event listener. This should work correctly when multiple dropdowns or modals use it simultaneously." — Prompt 12: "Create a useFetch<T>(url: string, options?: RequestInit) hook as a lightweight alternative to React Query for simple cases. Return { data: T | null, isLoading, error: Error | null, refetch }. Cancel the fetch on unmount using AbortController. Do not fire the request if url is an empty string." — Prompt 13: "Build a usePrevious<T>(value: T) hook that returns the previous render value of a variable. Use a ref to store the value from the last render. Useful for comparing prop changes, detecting direction of transitions, and animating between values." — Prompt 14: "Write a useMediaQuery(query: string) hook that returns a boolean indicating whether the media query matches. Use window.matchMedia and listen for changes. Return false during SSR. Example usage: const isMobile = useMediaQuery('(max-width: 768px)')."
Prompt 15: "Create a useIntersectionObserver hook that tracks whether a ref element is visible in the viewport. Props: threshold (default 0.1), rootMargin (default 0px). Return { ref, isVisible }. Assign ref to the target element. Useful for lazy loading, infinite scroll trigger points, and scroll-based animations." — Prompt 16: "Write a useWindowSize() hook that returns { width: number, height: number } and updates on window resize. Debounce the resize handler to avoid excessive re-renders. Return { width: 0, height: 0 } during SSR to avoid hydration mismatches."
Forms and Validation (Prompts 17–23)
Prompt 17: "Set up React Hook Form with a Zod resolver for a registration form. Fields: name, email, password, confirmPassword. Zod schema: name min 2 chars, email must be valid, password min 8 chars with at least one uppercase letter and one number, confirmPassword must match password using zod refine. Show how to display field-level errors below each input using the errors object from useForm." — Prompt 18: "Create a reusable FormField component that wraps a React Hook Form Controller with a label, error message, and helper text. It should work with any input component that accepts value and onChange. Show how to use it with a custom Select component and a standard HTML input in the same form."
Prompt 19: "Build a multi-step form with React Hook Form. Three steps: personal info, address, confirmation. Each step validates only its own fields before moving to the next using trigger() from useForm. Store values across steps. The confirmation step shows all entered values. On final submit, send all data to an API endpoint." — Prompt 20: "Write a form that supports adding and removing dynamic rows. Use the useFieldArray hook from React Hook Form to manage an array of line items, each with a name, quantity (number), and unitPrice (number). Add a computed total column that multiplies quantity by unitPrice. Show how to validate that at least one row exists before submission." — Prompt 21: "Create a masked input for phone number formatting using React Hook Form. As the user types, format the value as (123) 456-7890. Store the raw digits in the form state, not the formatted string. Validate that the raw value is exactly 10 digits. Show the formatted version in the input."
Prompt 22: "Add async server-side validation to a React Hook Form field. On the email field blur event, call an API endpoint to check if the email is already registered. If taken, set a custom error on the field using setError. Clear the error if the user changes the email. Do not block form submission while the check is in progress — show a loading indicator on the field instead." — Prompt 23: "Create a file upload form field with React Hook Form. The field should accept a FileList, validate that each file is under 5MB and is an image type, and show a preview thumbnail for each selected image. Register the field manually since HTML file inputs are uncontrolled. Show how to send the files using FormData in the submit handler."
Data Fetching With React Query (Prompts 24–31)
Prompt 24: "Set up TanStack Query v5 in a React application. Create a queryClient with default options: staleTime 1 minute, gcTime 5 minutes, retry 1 time on failure, and refetchOnWindowFocus disabled. Wrap the app with QueryClientProvider. Add ReactQueryDevtools in development only. Export the queryClient instance for use outside React components." — Prompt 25: "Write a useUsers(filters) custom hook using useQuery. The query key should include the filters so different filter combinations are cached separately. Fetch from /api/users with the filters as query params. Type the response as PaginatedResponse<User>. Return the data, isLoading, isError, and a refetch function."
Prompt 26: "Create a useCreateUser mutation hook using useMutation. On success, invalidate the users query cache using queryClient.invalidateQueries so the list refreshes automatically. On error, extract the error message from the API response. Return { mutate, isPending, error } — rename isLoading to isPending since that is the v5 name." — Prompt 27: "Implement optimistic updates for a toggle action (e.g. marking a task complete). In the onMutate callback, cancel in-flight queries, snapshot the current cache value, and apply the optimistic update. In onError, roll back to the snapshot. In onSettled, refetch to sync with the server. Show the TypeScript types for each callback." — Prompt 28: "Set up infinite scrolling with useInfiniteQuery. Fetch paginated items from an API that returns { items, nextCursor }. Configure getNextPageParam to return the nextCursor. Use the useIntersectionObserver hook to trigger fetchNextPage when the bottom sentinel element enters the viewport. Flatten the pages into a single items array for rendering."
Prompt 29: "Write a useOptimisticList hook that combines React Query cache manipulation with optimistic UI. It should support addItem (adds to cache immediately, rolls back on error), removeItem, and updateItem operations. The hook should work generically with any resource type that has an id field." — Prompt 30: "Create a prefetch utility for server-rendered pages. Write a prefetchUsers(queryClient, filters) function that calls queryClient.prefetchQuery with the same key and fetcher used by useUsers. Use it in the Next.js or Remix loader to populate the cache before the page renders so the client sees data immediately with no loading state." — Prompt 31: "Set up query error boundaries with React Query. Create a QueryErrorBoundary component using React Error Boundary that catches query errors. Show a fallback UI with the error message and a retry button that calls reset() to clear the error. Apply it around sections of the page that have independent data dependencies."
Performance Optimization (Prompts 32–39)
Prompt 32: "Wrap an expensive React component with React.memo. The component receives a user object and an onEdit callback as props. Add a custom comparison function to React.memo that only re-renders when user.id, user.name, or user.updatedAt changes — not when unrelated parent state changes. Show why a new object reference for user causes unnecessary re-renders even when the data is identical." — Prompt 33: "Refactor a component that recalculates a derived value on every render to use useMemo. The value is a sorted and filtered list derived from a large array prop. Show the before version (no memoization), the after version (with useMemo), and how to verify the optimization worked using the React DevTools Profiler."
Prompt 34: "Show the correct way to use useCallback to stabilize a function reference passed to a memoized child component. Explain when useCallback actually helps (when the child is wrapped in React.memo or when the function is a dependency of useEffect) and when it adds unnecessary complexity without benefit." — Prompt 35: "Implement code splitting with React.lazy and Suspense. Split a dashboard application so that the Analytics, Settings, and Billing route components are loaded on demand. Show a loading fallback for each split chunk. Add error handling with an ErrorBoundary so a failed chunk load shows a retry button rather than a blank screen." — Prompt 36: "Add virtualization to a long list using react-virtual (TanStack Virtual). The list renders 10,000 items. Each row has a variable estimated height. Show the correct hook setup, the container div with overflow-y: auto, the total size padding div, and how to render only visible rows. Compare DOM node count before and after."
Prompt 37: "Optimize images in a React application. Show how to use a custom Image component that: sets explicit width and height to prevent layout shift, uses loading='lazy' for below-fold images, provides a low-quality placeholder blur, and falls back to a placeholder SVG if the image fails to load." — Prompt 38: "Set up a web worker for a CPU-intensive operation (e.g. parsing a large CSV). Move the parsing logic into a worker file. Communicate with the worker from React using useEffect. Show how to handle the async message response and how to terminate the worker when the component unmounts." — Prompt 39: "Profile and fix a React component that re-renders too frequently. Use the React DevTools Profiler to record a interaction. Identify the components that render when they should not. Apply the correct fix for each case: React.memo for pure components, context splitting for global state causing broad re-renders, and selector patterns for derived state."
Testing With Vitest and Testing Library (Prompts 40–50)
Prompt 40: "Set up Vitest with React Testing Library for a Vite + React + TypeScript project. Install vitest, @vitest/ui, jsdom, @testing-library/react, @testing-library/user-event, and @testing-library/jest-dom. Create a vitest.config.ts with jsdom as the environment and a setup file that imports @testing-library/jest-dom. Add a test script to package.json." — Prompt 41: "Write tests for the Button component. Test: renders with the correct text, applies the primary variant class, shows a spinner when isLoading is true, is disabled when isLoading is true, is disabled when disabled prop is true, calls onClick when clicked, and does not call onClick when disabled. Use userEvent from @testing-library/user-event for click interactions."
Prompt 42: "Write tests for a controlled form with React Hook Form. Test: submits correct data when valid, shows validation errors when required fields are empty, shows a password mismatch error when confirmPassword differs, and calls the onSubmit prop with form values on successful submission. Use userEvent.type to simulate typing." — Prompt 43: "Test an async component that fetches data using React Query. Use msw (Mock Service Worker) to intercept the API call and return mock data. Test: shows a loading state initially, renders the data after the request resolves, and shows an error message when the request fails (return a 500 from msw). Wrap the component with QueryClientProvider in the test." — Prompt 44: "Write tests for a custom hook. Use renderHook from @testing-library/react to test the useLocalStorage hook. Test: returns the initialValue when nothing is stored, reads an existing value from localStorage on mount, updates localStorage when setValue is called, and removes the key when removeValue is called. Mock localStorage with vi.spyOn."
Prompt 45: "Test a component that uses React Router. Wrap the component in MemoryRouter in the test. Test: renders the correct component for a given route, navigates to a new route when a link is clicked, shows a 404 component for an unknown route. Use screen.getByRole and userEvent.click for interactions." — Prompt 46: "Write snapshot tests for a UI component library. Add a Storybook story for the Button component in all variant and size combinations. Use @storybook/test to write interaction tests that click the button and verify the onClick handler is called. Explain when snapshot tests are useful and when they create more noise than value." — Prompt 47: "Set up code coverage with Vitest. Add a coverage script that runs vitest run --coverage using @vitest/coverage-v8. Configure thresholds of 70% for branches and 80% for functions and lines. Generate both text and lcov reports. Show how to exclude test files, config files, and type-only files from the coverage report."
Prompt 48: "Write tests for the Modal component. Test: does not render when isOpen is false, renders children when isOpen is true, calls onClose when the overlay is clicked, calls onClose when Escape is pressed, does not call onClose when hideCloseButton is false and the inner content is clicked. Verify the modal renders in a Portal outside the main app div." — Prompt 49: "Test the useInfiniteQuery implementation for an infinite scroll list. Mock the paginated API with msw. Test: renders the first page of items, shows a loading indicator while fetching, appends the next page when fetchNextPage is called, shows an end-of-list message when hasNextPage is false." — Prompt 50: "Create a testing utilities file at src/tests/utils.tsx that exports a renderWithProviders(ui, options?) function. It should wrap the component with QueryClientProvider (fresh client per test), BrowserRouter, and any other global providers your app needs. This keeps test boilerplate minimal and makes provider wrapping consistent across all test files."
FAQ
Should I use React Query or SWR for data fetching?
React Query (TanStack Query) is the stronger choice for most applications. It handles mutations, optimistic updates, infinite queries, and cache invalidation more explicitly than SWR. SWR is simpler to learn and works well for read-heavy apps with straightforward cache requirements. For a new project with mutations and complex data dependencies, React Query is worth the extra surface area.
Do these prompts work with the Next.js App Router?
The component, hook, form, and testing prompts work with any React setup. The data fetching prompts need adjustment for App Router since React Query client-side patterns do not apply directly to Server Components. Use the prefetch pattern from Prompt 30 and add "this is for a Next.js Client Component" when running data fetching prompts.
Is Vitest a drop-in replacement for Jest in React projects?
For React Testing Library usage, yes — the API is identical. Vitest is faster, uses native ESM, and integrates with Vite without a separate babel config. If your project already uses Jest and is working well, there is no urgent reason to switch.
When should I avoid React.memo?
Avoid it when the component renders quickly, when props change on almost every parent render anyway, and when the comparison function itself is expensive. React.memo has overhead — a small component that renders in under 1ms will be slower with memo if the comparison takes longer than the render.
How do I type a component that accepts both ref and custom props?
Use React.forwardRef with an explicit generic: React.forwardRef<HTMLInputElement, InputProps>((props, ref) => ...). Then wrap it with React.memo if needed. The generic parameters are type of the ref element first, then the props type — the opposite order from what many developers expect the first time.
Related free tools
If you want to turn this topic into action, use one of ShortIQ's free tools for campaign planning, UTM structure, or QR distribution.
Continue Reading
Explore more guides on link shortener SaaS strategy, Bitly alternatives, and white label link management.
Was this article helpful?
Tell us if this guide solved the problem or what was still missing. We use this to improve the blog and only follow up if you explicitly allow it.