React has evolved from a simple UI library into a full-fledged ecosystem where performance, maintainability, and developer experience go hand in hand. As a frontend developer, you know that writing components is only half the battle — the other half is ensuring they're scalable, reusable, and fast. That's where AI-powered code generation comes in. With the right prompts, you can transform a vague idea into a well-structured, optimized React component in seconds. In this article, I'll share ten battle-tested prompts that cover everything from basic function components to advanced performance optimizations like memoization and code splitting. Whether you're a junior dev looking to speed up your workflow or a senior architect aiming to enforce best practices, these prompts will become your secret weapon.
1. The Basic Functional Component
Prompt: "Generate a React functional component named Button with the following props: children, onClick, disabled, variant. The component should be typed with TypeScript and include a default export. Use inline styles for the variant (primary, secondary, danger) and handle the disabled state."
Why it works: This prompt is perfect for kickstarting a project. It specifies the component name, props, and even the styling approach, ensuring the output is ready to use.
Example output:
import React from 'react';
interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
variant?: 'primary'
| 'secondary' | 'danger';
}
const Button: React.FC<ButtonProps> = ({
children,
onClick,
disabled = false,
variant = 'primary'
}) => {
const styles = {
primary: { backgroundColor: '#0070f3', color: 'white' },
secondary: { backgroundColor: '#eaeaea', color: 'black' },
danger: { backgroundColor: '#e00', color: 'white' }
};
return (
<button
onClick={onClick}
disabled={disabled}
style={{
...styles[variant],
opacity: disabled ? 0.5 : 1,
cursor: disabled ? 'not-allowed' : 'pointer',
padding: '10px 20px',
borderRadius: '4px',
fontSize: '16px'
}}
>
{children}
</button>
);
};
export default Button;
How to adapt: Change the prop names, styling approach (e.g., CSS Modules), or add more variants to fit your design system.
2. Component with State and Lifecycle
Prompt: "Create a React component Counter that uses the useState and useEffect hooks. It should display a count, increment/decrement buttons, and a reset button. The component should also log to the console whenever the count changes. Use TypeScript."
Why it works: This prompt is ideal for learning or prototyping stateful logic. It explicitly mentions the hooks and the logging behavior, which is often needed for debugging.
Example output:
import React, { useState, useEffect } from 'react';
const Counter: React.FC = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Count changed to:', count);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
};
export default Counter;
How to adapt: You can ask for additional features like a step prop, or use useReducer for more complex state logic.
3. Fetching Data with Custom Hook
Prompt: "Write a custom hook useFetch that takes a URL string as a parameter and returns { data, loading, error }. The hook should use useState and useEffect to fetch data from the given URL, handle loading and error states, and allow refetching by calling a refetch function. Use TypeScript generics for the data type."
Why it works: Data fetching is a common task, and a reusable hook is a great addition to any codebase. This prompt ensures the hook is typed and has proper error handling.
Example output:
import { useState, useEffect, useCallback } from 'react';
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): FetchState<T> & { refetch: () => void } {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null
});
const refetch = useCallback(() => {
setState({ data: null, loading: true, error: null });
fetch(url)
.then((res) => {
if (!res.ok) throw new Error('Network response was not ok');
return res.json() as Promise<T>;
})
.then((data) => setState({ data, loading: false, error: null }))
.catch((error) => setState({ data: null, loading: false, error }));
}, [url]);
useEffect(() => {
refetch();
}, [refetch]);
return { ...state, refetch };
}
export default useFetch;
How to adapt: You can add a request cancellation feature using AbortController or support for query parameters.
4. Optimizing with React.memo
Prompt: "Wrap the Button component from the first prompt with React.memo to prevent unnecessary re-renders. Also, use useCallback in a parent component to memoize the onClick handler. Provide a full example showing the parent and child."
Why it works: This prompt addresses performance optimization, a key concern in React. It shows how to apply React.memo and useCallback together correctly.
Example output:
import React, { useState, useCallback } from 'react';
// Button component (same as prompt 1 but wrapped with memo)
const Button = React.memo(({ children, onClick, disabled, variant }: ButtonProps) => {
// ... same implementation
});
// Parent component
const Parent: React.FC = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((c) => c + 1);
}, []);
return (
<div>
<Button onClick={handleClick} variant="primary">
Clicked {count} times
</Button>
</div>
);
};
How to adapt: You can specify custom comparison logic for React.memo or use useMemo for expensive calculations.
5. Code Splitting with React.lazy
Prompt: "Create a component Dashboard that uses React.lazy to load the Chart component on demand. Use Suspense to show a fallback while loading. Also, export the Chart component as a default export. Provide the full code for both components."
Why it works: Code splitting is crucial for large applications. This prompt demonstrates how to implement lazy loading with a real-world example.
Example output:
import React, { Suspense, lazy } from 'react';
const Chart = lazy(() => import('./Chart'));
const Dashboard: React.FC = () => {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading chart...</div>}>
<Chart />
</Suspense>
</div>
);
};
export default Dashboard;
// Chart.tsx
const Chart: React.FC = () => {
return <div>Chart component</div>;
};
export default Chart;
How to adapt: You can use named exports and specify the webpackChunkName for better chunk names.
6. Form Handling with Uncontrolled Inputs
Prompt: "Build a form component UserForm that uses uncontrolled inputs (using ref) to collect name and email. On submit, the form should display the entered values in a console log. Use TypeScript and functional components."
Why it works: Uncontrolled inputs can improve performance in some cases and are simpler for basic forms. This prompt shows how to use useRef effectively.
Example output:
import React, { useRef } from 'react';
const UserForm: React.FC = () => {
const nameRef = useRef<HTMLInputElement>(null);
const emailRef = useRef<HTMLInputElement>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
console.log('Name:', nameRef.current?.value);
console.log('Email:', emailRef.current?.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameRef} placeholder="Name" />
<input type="email" ref={emailRef} placeholder="Email" />
<button type="submit">Submit</button>
</form>
);
};
export default UserForm;
How to adapt: You can add validation logic or use useState for controlled inputs instead.
7. Context API and Provider
Prompt: "Create a theme context using React Context API. The context should provide a theme object (dark or light) and a toggleTheme function. Implement a ThemeProvider component that wraps children and provides the context value. Also, create a custom hook useTheme that throws an error if used outside the provider. Use TypeScript."
Why it works: Context API is fundamental for state sharing. This prompt ensures proper typing and error handling, which is essential for production code.
Example output:
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) throw new Error('useTheme must be used within a ThemeProvider');
return context;
};
How to adapt: You can add more fields to the context or use useReducer for complex state.
8. Performance Profiling with useMemo
Prompt: "Create a component ExpensiveList that renders a list of numbers and calculates their squares. Use useMemo to memoize the list of squared numbers so that it only recalculates when the input list changes. Also, use useEffect to log render times. Provide the full component."
Why it works: This prompt teaches useMemo in a practical scenario, showing how to prevent costly calculations on every render.
Example output:
import React, { useMemo, useState, useEffect } from 'react';
const ExpensiveList: React.FC = () => {
const [numbers, setNumbers] = useState<number[]>([1, 2, 3, 4, 5]);
const [input, setInput] = useState('');
const squaredNumbers = useMemo(() => {
return numbers.map((n) => n * n);
}, [numbers]);
useEffect(() => {
console.log('Render time:', new Date().toISOString());
});
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add number"
/>
<button onClick={() => setNumbers([...numbers, parseInt(input)])}>
Add
</button>
<ul>
{squaredNumbers.map((num, idx) => (
<li key={idx}>{num}</li>
))}
</ul>
</div>
);
};
export default ExpensiveList;
How to adapt: You can replace the square calculation with a more complex function or use useMemo for object references.
9. Higher-Order Component (HOC)
Prompt: "Write a Higher-Order Component withLoading that takes a component and returns a new component that shows a loading spinner while a loading prop is true. The HOC should pass through all other props. Use TypeScript."
Why it works: HOCs are a classic React pattern for code reuse. This prompt demonstrates how to create one with proper typing.
Example output:
import React from 'react';
interface WithLoadingProps {
loading: boolean;
}
const withLoading = <P extends object>(Component: React.ComponentType<P>) => {
return ({ loading, ...props }: WithLoadingProps & P) => {
if (loading) {
return <div>Loading...</div>;
}
return <Component {...props as P} />;
};
};
// Usage
const DataComponent = ({ data }: { data: string }) => <div>{data}</div>;
const DataWithLoading = withLoading(DataComponent);
export default DataWithLoading;
How to adapt: You can add error handling or pass custom spinner components.
10. Compound Components Pattern
Prompt: "Create a Tabs component using the compound components pattern. The Tabs component should have TabList, Tab, and TabPanels as subcomponents. Manage the active tab state internally and provide a useTabs hook to access the context. Use TypeScript."
Why it works: Compound components are an advanced pattern for building flexible, reusable components. This prompt guides the generation of a complex structure.
Example output:
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface TabsContextType {
activeIndex: number;
setActiveIndex: (index: number) => void;
}
const TabsContext = createContext<TabsContextType | undefined>(undefined);
interface TabsProps {
children: ReactNode;
}
const Tabs: React.FC<TabsProps> = ({ children }) => {
const [activeIndex, setActiveIndex] = useState(0);
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
<div>{children}</div>
</TabsContext.Provider>
);
};
interface TabListProps {
children: ReactNode;
}
const TabList: React.FC<TabListProps> = ({ children }) => (
<div style={{ display: 'flex', gap: '10px' }}>{children}</div>
);
interface TabProps {
index: number;
children: ReactNode;
}
const Tab: React.FC<TabProps> = ({ index, children }) => {
const context = useContext(TabsContext);
if (!context) throw new Error('Tab must be used within Tabs');
const { activeIndex, setActiveIndex } = context;
return (
<button
onClick={() => setActiveIndex(index)}
style={{ fontWeight: activeIndex === index ? 'bold' : 'normal' }}
>
{children}
</button>
);
};
interface TabPanelsProps {
children: ReactNode;
}
const TabPanels: React.FC<TabPanelsProps> = ({ children }) => (
<div>{children}</div>
);
interface TabPanelProps {
index: number;
children: ReactNode;
}
const TabPanel: React.FC<TabPanelProps> = ({ index, children }) => {
const context = useContext(TabsContext);
if (!context) throw new Error('TabPanel must be used within Tabs');
const { activeIndex } = context;
return activeIndex === index ? <div>{children}</div> : null;
};
// Attach subcomponents
Tabs.TabList = TabList;
Tabs.Tab = Tab;
Tabs.TabPanels = TabPanels;
Tabs.TabPanel = TabPanel;
export default Tabs;
How to adapt: You can add keyboard navigation or allow controlled mode via props.
Putting It All Together
These ten prompts cover a wide range of React patterns and techniques. By incorporating them into your workflow, you can generate solid, optimized components in no time. Remember to always review AI-generated code for edge cases and ensure it aligns with your project's style guide. The real power of AI lies in its ability to handle boilerplate, allowing you to focus on the unique logic of your application.
Now go ahead and try these prompts in your next project. You'll be amazed at how quickly you can prototype, optimize, and ship React components with the help of AI. If you have your own favorite prompts, share them in the comments below — let's build a community of efficient developers!
Comments