Intermediate React Interview Questions
Questions for mid-level React developers.
Q1: Explain the React component lifecycle
Answer: Components go through phases: Mounting, Updating, Unmounting.
1function Component() {
2 // Mounting: Component is created
3 useEffect(() => {
4 console.log('Component mounted')
5
6 // Unmounting: Cleanup
7 return () => {
8 console.log('Component will unmount')
9 }
10 }, [])
11
12 // Updating: Dependencies changed
13 useEffect(() => {
14 console.log('Dependencies updated')
15 }, [dep1, dep2])
16}Class component methods:
componentDidMount→useEffect(() => {}, [])componentDidUpdate→useEffect(() => {}, [deps])componentWillUnmount→useEffect(() => cleanup, [])
Q2: What is the difference between useEffect and useLayoutEffect?
Answer:
| useEffect | useLayoutEffect |
|---|---|
| Runs after paint | Runs before paint |
| Asynchronous | Synchronous |
| Won't block visual updates | May block visual updates |
| Most common choice | For DOM measurements/mutations |
1// useEffect - runs after browser paints
2useEffect(() => {
3 // Most side effects go here
4}, [])
5
6// useLayoutEffect - runs before browser paints
7useLayoutEffect(() => {
8 // DOM measurements, prevents flicker
9 const rect = ref.current.getBoundingClientRect()
10}, [])Q3: How does React.memo work?
Answer: React.memo is a higher-order component that memoizes functional components, preventing re-renders if props haven't changed.
1const MemoizedComponent = React.memo(function Component({ name }) {
2 console.log('Rendering')
3 return <div>{name}</div>
4})
5
6// Custom comparison
7const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => {
8 return prevProps.id === nextProps.id
9})Use when:
- Component renders often with same props
- Component is expensive to render
- Parent re-renders frequently
Q4: Explain useMemo vs useCallback
Answer:
1// useMemo - memoizes a VALUE
2const sortedItems = useMemo(() => {
3 return items.sort((a, b) => a.name.localeCompare(b.name))
4}, [items])
5
6// useCallback - memoizes a FUNCTION
7const handleClick = useCallback(() => {
8 console.log(count)
9}, [count])
10
11// They're equivalent:
12useCallback(fn, deps) === useMemo(() => fn, deps)Use useMemo for:
- Expensive calculations
- Objects/arrays passed as props
Use useCallback for:
- Event handlers passed to memoized children
- Dependencies in useEffect
Q5: What are error boundaries?
Answer: Error boundaries catch JavaScript errors in child components and display fallback UI.
1class ErrorBoundary extends React.Component {
2 state = { hasError: false }
3
4 static getDerivedStateFromError(error) {
5 return { hasError: true }
6 }
7
8 componentDidCatch(error, errorInfo) {
9 console.error('Error:', error, errorInfo)
10 }
11
12 render() {
13 if (this.state.hasError) {
14 return <h1>Something went wrong.</h1>
15 }
16 return this.props.children
17 }
18}
19
20// Usage
21<ErrorBoundary>
22 <MyComponent />
23</ErrorBoundary>Note: Error boundaries don't catch:
- Event handler errors
- Async code errors
- Server-side rendering errors
- Errors in the boundary itself
Q6: How do you optimize React performance?
Answer:
- Memoization
1const MemoizedChild = React.memo(Child)
2const value = useMemo(() => compute(a, b), [a, b])
3const handler = useCallback(() => {}, [deps])- Code Splitting
1const LazyComponent = lazy(() => import('./Component'))- Virtualization (for long lists)
1import { useVirtualizer } from '@tanstack/react-virtual'- Proper key usage
1{items.map(item => <Item key={item.id} />)}- Avoid inline objects/functions
1// ❌ Creates new object every render
2<Child style={{ color: 'red' }} />
3
4// ✅ Stable reference
5const style = useMemo(() => ({ color: 'red' }), [])
6<Child style={style} />Q7: What are React portals?
Answer: Portals render children into a different DOM node outside the parent hierarchy.
1import { createPortal } from 'react-dom'
2
3function Modal({ children }) {
4 return createPortal(
5 <div className="modal">{children}</div>,
6 document.body
7 )
8}Use cases:
- Modals/dialogs
- Tooltips
- Dropdowns
- Toast notifications
Q8: Explain Context API and its limitations
Answer: Context provides a way to share values between components without prop drilling.
1const ThemeContext = createContext('light')
2
3function App() {
4 return (
5 <ThemeContext.Provider value="dark">
6 <Page />
7 </ThemeContext.Provider>
8 )
9}
10
11function Button() {
12 const theme = useContext(ThemeContext)
13 return <button className={theme}>Click</button>
14}Limitations:
- All consumers re-render when context changes
- Not suitable for frequently changing data
- Can cause performance issues if overused
Solutions:
- Split contexts by update frequency
- Use memoization
- Consider state management libraries
Q9: What is reconciliation?
Answer: Reconciliation is React's algorithm for diffing two trees and determining which parts need to be updated.
Key assumptions:
- Different element types produce different trees
- Keys identify stable children across renders
Process:
- Compare root elements
- If different types → rebuild entire tree
- If same type → update attributes, recurse on children
- Use keys to match children efficiently
Q10: How do you handle forms in React?
Answer:
1// Controlled component
2function Form() {
3 const [form, setForm] = useState({ email: '', password: '' })
4 const [errors, setErrors] = useState({})
5
6 const handleChange = (e) => {
7 const { name, value } = e.target
8 setForm(prev => ({ ...prev, [name]: value }))
9 }
10
11 const validate = () => {
12 const newErrors = {}
13 if (!form.email) newErrors.email = 'Required'
14 if (!form.password) newErrors.password = 'Required'
15 setErrors(newErrors)
16 return Object.keys(newErrors).length === 0
17 }
18
19 const handleSubmit = (e) => {
20 e.preventDefault()
21 if (validate()) {
22 // Submit form
23 }
24 }
25
26 return (
27 <form onSubmit={handleSubmit}>
28 <input name="email" value={form.email} onChange={handleChange} />
29 {errors.email && <span>{errors.email}</span>}
30 <button type="submit">Submit</button>
31 </form>
32 )
33}For complex forms, consider: React Hook Form, Formik
Q11: What is the difference between createElement and cloneElement?
Answer:
1// createElement - creates new element
2const element = React.createElement('div', { className: 'box' }, 'Hello')
3// Equivalent to: <div className="box">Hello</div>
4
5// cloneElement - clones existing element with new props
6const cloned = React.cloneElement(element, { id: 'main' })
7// Adds id="main" to the elementcloneElement use cases:
- Adding props to children
- Modifying children in parent
Q12: How do you share logic between components?
Answer:
- Custom Hooks (preferred)
1function useWindowSize() {
2 const [size, setSize] = useState({ width: 0, height: 0 })
3 // ... logic
4 return size
5}- Render Props
1<DataFetcher url="/api/data">
2 {({ data, loading }) => loading ? <Spinner /> : <List data={data} />}
3</DataFetcher>- Higher Order Components
1const EnhancedComponent = withAuth(MyComponent)Custom hooks are the modern, preferred approach!
