Advanced React Patterns
React patterns are reusable solutions to common problems. These patterns help you write more maintainable, flexible, and reusable code.
Compound Components Pattern
Compound components work together to form a complete UI element, sharing implicit state:
import { createContext , useContext , useState } from 'react' ;
import AccordionItem from './AccordionItem.jsx' ;
import AccordionTitle from './AccordionTitle.jsx' ;
import AccordionContent from './AccordionContent.jsx' ;
const AccordionContext = createContext ();
export function useAccordionContext () {
const ctx = useContext ( AccordionContext );
if ( ! ctx ) {
throw new Error (
'Accordion-related components must be wrapped by <Accordion>.'
);
}
return ctx ;
}
export default function Accordion ({ children , className }) {
const [ openItemId , setOpenItemId ] = useState ();
function toggleItem ( id ) {
setOpenItemId (( prevId ) => ( prevId === id ? null : id ));
}
const contextValue = {
openItemId ,
toggleItem ,
};
return (
< AccordionContext.Provider value = { contextValue } >
< ul className = { className } > { children } </ ul >
</ AccordionContext.Provider >
);
}
Accordion . Item = AccordionItem ;
Accordion . Title = AccordionTitle ;
Accordion . Content = AccordionContent ;
Using Compound Components
import Accordion from './components/Accordion/Accordion.jsx' ;
function App () {
return (
< main >
< section >
< h2 > Why work with us? </ h2 >
< Accordion className = "accordion" >
< Accordion.Item className = "accordion-item" >
< Accordion.Title className = "accordion-item-title" id = "experience" >
We got 20 years of experience
</ Accordion.Title >
< Accordion.Content
className = "accordion-item-content"
id = "experience"
>
< article >
< p > You can't go wrong with us. </ p >
< p >
We are in the business of planning highly individualized
vacation trips for more than 20 years.
</ p >
</ article >
</ Accordion.Content >
</ Accordion.Item >
< Accordion.Item className = "accordion-item" >
< Accordion.Title id = "local-guides" className = "accordion-item-title" >
We are working with local guides
</ Accordion.Title >
< Accordion.Content
id = "local-guides"
className = "accordion-item-content"
>
< article >
< p > We are not doing this along from our office. </ p >
< p >
Instead, we are working with local guides to ensure a safe and
pleasant vacation.
</ p >
</ article >
</ Accordion.Content >
</ Accordion.Item >
</ Accordion >
</ section >
</ main >
);
}
Compound components provide a clean, declarative API and keep related components tightly coupled.
Benefits of Compound Components
Flexible API
Users can compose components in various ways while maintaining shared state.
Separation of Concerns
Each sub-component handles its own rendering logic while sharing context.
Implicit State Sharing
State is managed in the parent and automatically available to all children through context.
Better Developer Experience
Clear, semantic component structure that’s easy to understand and use.
Render Props Pattern
Pass a function as a prop to share code between components:
export default function SearchableList ({ items , itemKeyFn , children }) {
const [ searchTerm , setSearchTerm ] = useState ( '' );
const searchResults = items . filter (( item ) =>
JSON . stringify ( item ). toLowerCase (). includes ( searchTerm . toLowerCase ())
);
function handleChange ( event ) {
setSearchTerm ( event . target . value );
}
return (
< div className = "searchable-list" >
< input type = "search" placeholder = "Search" onChange = { handleChange } />
< ul >
{ searchResults . map (( item ) => (
< li key = { itemKeyFn ( item ) } >
{ children ( item ) }
</ li >
)) }
</ ul >
</ div >
);
}
Using Render Props
import SearchableList from './components/SearchableList/SearchableList' ;
const PLACES = [
{ id: 'p1' , title: 'Forest Adventure' , image: 'forest.jpg' },
{ id: 'p2' , title: 'Beach Paradise' , image: 'beach.jpg' },
];
function App () {
return (
< main >
< SearchableList items = { PLACES } itemKeyFn = { ( item ) => item . id } >
{ ( item ) => (
< article >
< img src = { item . image } alt = { item . title } />
< h3 > { item . title } </ h3 >
</ article >
) }
</ SearchableList >
</ main >
);
}
Render props allow the component to be flexible about what it renders while managing the logic internally.
Debouncing Pattern
Delay expensive operations until user stops typing:
import { useRef , useState } from 'react' ;
export default function SearchableList ({ items , itemKeyFn , children }) {
const lastChange = useRef ();
const [ searchTerm , setSearchTerm ] = useState ( '' );
const searchResults = items . filter (( item ) =>
JSON . stringify ( item ). toLowerCase (). includes ( searchTerm . toLowerCase ())
);
function handleChange ( event ) {
if ( lastChange . current ) {
clearTimeout ( lastChange . current );
}
lastChange . current = setTimeout (() => {
lastChange . current = null ;
setSearchTerm ( event . target . value );
}, 500 );
}
return (
< div className = "searchable-list" >
< input type = "search" placeholder = "Search" onChange = { handleChange } />
< ul >
{ searchResults . map (( item ) => (
< li key = { itemKeyFn ( item ) } > { children ( item ) } </ li >
)) }
</ ul >
</ div >
);
}
Always clear timeouts when the component unmounts to prevent memory leaks.
Higher-Order Components (HOC)
A function that takes a component and returns a new enhanced component:
function withLoading ( Component ) {
return function WithLoadingComponent ({ isLoading , ... props }) {
if ( isLoading ) {
return < div > Loading... </ div > ;
}
return < Component { ... props } /> ;
};
}
// Usage
const UserListWithLoading = withLoading ( UserList );
function App () {
const [ isLoading , setIsLoading ] = useState ( true );
const [ users , setUsers ] = useState ([]);
return < UserListWithLoading isLoading = { isLoading } users = { users } /> ;
}
Container/Presentational Pattern
Separate logic from presentation:
Container (Logic)
Presentational (UI)
function UserListContainer () {
const [ users , setUsers ] = useState ([]);
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
fetchUsers (). then ( data => {
setUsers ( data );
setLoading ( false );
});
}, []);
if ( loading ) return < Loading /> ;
return < UserList users = { users } /> ;
}
Custom Hook Pattern
Extract reusable logic into custom hooks:
function useToggle ( initialValue = false ) {
const [ value , setValue ] = useState ( initialValue );
const toggle = useCallback (() => {
setValue ( v => ! v );
}, []);
return [ value , toggle ];
}
// Usage
function Modal () {
const [ isOpen , toggleOpen ] = useToggle ( false );
return (
<>
< button onClick = { toggleOpen } > Open Modal </ button >
{ isOpen && < div > Modal Content </ div > }
</>
);
}
State Reducer Pattern
Give users control over internal state logic:
function toggleReducer ( state , action ) {
switch ( action . type ) {
case 'TOGGLE' :
return { on: ! state . on };
case 'RESET' :
return { on: false };
default :
return state ;
}
}
function useToggle () {
const [ state , dispatch ] = useReducer ( toggleReducer , { on: false });
const toggle = () => dispatch ({ type: 'TOGGLE' });
const reset = () => dispatch ({ type: 'RESET' });
return { on: state . on , toggle , reset };
}
Pattern Comparison
When to use Compound Components
Building complex UI components with multiple related parts
Need tight coupling between parent and children
Want a clean, declarative API
Examples: Accordions, Tabs, Select dropdowns
Need to share code between components
Want flexibility in what gets rendered
Logic is reusable but UI varies
Examples: Data fetching, animations, form handling
Extracting stateful logic
Sharing logic across multiple components
Simplifying complex components
Examples: Form handling, data fetching, subscriptions
Need to enhance multiple components with same logic
Working with legacy code
Cross-cutting concerns like logging, auth
Note: Custom hooks are often preferred in modern React
Best Practices
Composition over Inheritance React favors composition. Build complex UIs from simple, reusable components.
Single Responsibility Each component should do one thing well. Split complex components into smaller ones.
Props vs Context Use props for explicit data flow, context for deeply nested data needs.
Keep Components Pure Same props should always produce same output. Avoid side effects in render.
Source Code : Section 27 - Patterns