|
All the 24 design patterns contextualized in the frontend-react-js world
🧱 1. Creational Patterns
Goal: Abstract the object creation process to make it more flexible.
1.1 Singleton
Ensures a class has only one instance and provides a global point of access to it.
const ThemeContext = React.createContext('light'); const App = () => ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> );
1.2 Factory Method
Defines an interface for creating an object, but allows subclasses to alter the type of objects that will be created.
const createButton = (type) => { switch (type) { case 'primary': return (props) => <button className="btn-primary" {...props} />; case 'secondary': return (props) => <button className="btn-secondary" {...props} />; default: return (props) => <button {...props} />; } }; const PrimaryButton = createButton('primary');
1.3 Abstract Factory
Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
const ButtonFactory = (theme) => { const themes = { light: (props) => <button style={{ backgroundColor: 'white' }} {...props} />, dark: (props) => <button style={{ backgroundColor: 'black' }} {...props} />, }; return themes[theme]; }; const ThemedButton = ButtonFactory('dark');
1.4 Builder
Separates the construction of a complex object from its representation so that the same construction process can create different representations.
const useQuery = () => { const [query, setQuery] = React.useState({}); const addFilter = (key, value) => setQuery((q) => ({ ...q, [key]: value })); const removeFilter = (key) => setQuery((q) => { const newQuery = { ...q }; delete newQuery[key]; return newQuery; }); return { query, addFilter, removeFilter }; };
1.5 Prototype
Specifies the kinds of objects to create using a prototypical instance and creates new objects by copying this prototype.
const baseComponent = (props) => <div style={{ color: 'blue' }} {...props} />; const clonedComponent = (props) => React.cloneElement(baseComponent(props), { style: { color: 'red' } });
🏗️ 2. Structural Patterns
Goal: Simplify the design by identifying a simple way to realize relationships between entities.
2.1 Adapter
Allows the interface of an existing class to be used as another interface.
const OldComponent = ({ text }) => <div>{text}</div>; const AdapterComponent = ({ content }) => <OldComponent text={content} />;
2.2 Bridge
Decouples an abstraction from its implementation so that the two can vary independently.
const ThemeContext = React.createContext(); const ThemedButton = () => { const theme = React.useContext(ThemeContext); return <button style={{ backgroundColor: theme.background }}>Click me</button>; };
2.3 Composite
Composes objects into tree structures to represent part-whole hierarchies.
const MenuItem = ({ label, children }) => ( <div> <div>{label}</div> {children && <div className="submenu">{children}</div>} </div> ); const Menu = () => ( <MenuItem label="File"> <MenuItem label="New" /> <MenuItem label="Open" /> </MenuItem> );
2.4 Decorator
Attaches additional responsibilities to an object dynamically.
const withLogging = (Component) => (props) => { React.useEffect(() => { console.log('Component mounted'); return () => console.log('Component unmounted'); }, []); return <Component {...props} />; }; const EnhancedComponent = withLogging(SomeComponent);
2.5 Facade
Provides a simplified interface to a larger body of code.
const useAPI = () => { const fetchData = async (endpoint) => { const response = await fetch(endpoint); return response.json(); }; return { fetchData }; }; const DataFetcher = ({ endpoint }) => { const { fetchData } = useAPI(); const [data, setData] = React.useState(null); React.useEffect(() => { fetchData(endpoint).then(setData); }, [endpoint]); return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>; };
2.6 Flyweight
Uses sharing to support large numbers of fine-grained objects efficiently.
const heavyData = new Map(); const getHeavyData = (key) => { if (!heavyData.has(key)) { heavyData.set(key, computeHeavyData(key)); } return heavyData.get(key); };
2.7 Proxy
Provide a placeholder to control access to another object. In React, it's commonly used for authorization, lazy loading, or logging wrappers.
const withAuth = (Component) => (props) => { const isAuthenticated = useAuth(); // Custom hook or context if (!isAuthenticated) { return <p>You must log in to access this page.</p>; } return <Component {...props} />; }; // Usage const Dashboard = () => <div>Private content</div>; const ProtectedDashboard = withAuth(Dashboard);
🔁 3. Behavioral Patterns
Goal: Handle communication, delegation, and responsibility between objects.
3.1 Observer
React built-in! Listens for state changes and reacts.
const Counter = () => { const [count, setCount] = React.useState(0); React.useEffect(() => { console.log('Count updated:', count); }, [count]); return <button onClick={() => setCount(count + 1)}>{count}</button>; };
3.2 Strategy
Select behavior at runtime.
const UpperCase = ({ text }) => <p>{text.toUpperCase()}</p>; const LowerCase = ({ text }) => <p>{text.toLowerCase()}</p>; const TextFormatter = ({ text, strategy: Strategy }) => <Strategy text={text} />; // <TextFormatter text="Hello" strategy={UpperCase} />
3.3 Command
Encapsulate actions as objects/functions.
const useCommand = () => { const [log, setLog] = React.useState([]); const execute = (command) => { command(); setLog((prev) => [...prev, command.name]); }; return { execute, log }; }; const sayHello = () => alert("Hello!"); const App = () => { const { execute } = useCommand(); return <button onClick={() => execute(sayHello)}>Run</button>; };
3.4 Chain of Responsibility
Pass request along a chain of handlers.
const withPermission = (Component) => (props) => props.user.isAdmin ? <Component {...props} /> : <p>Access Denied</p>; const withLogging = (Component) => (props) => { console.log("Rendering", Component.name); return <Component {...props} />; }; const AdminPanel = (props) => <div>Admin Stuff</div>; const ProtectedAdmin = withLogging(withPermission(AdminPanel));
3.5 Mediator
Centralize communication between components.
const MediatorContext = React.createContext(); const MediatorProvider = ({ children }) => { const [message, setMessage] = React.useState(""); return ( <MediatorContext.Provider value={{ message, setMessage }}> {children} </MediatorContext.Provider> ); }; const Sender = () => { const { setMessage } = React.useContext(MediatorContext); return <button onClick={() => setMessage("Hi from Sender")}>Send</button>; }; const Receiver = () => { const { message } = React.useContext(MediatorContext); return <p>Received: {message}</p>; };
3.6 Memento
Capture and restore object state.
const useMemento = () => { const [history, setHistory] = React.useState([]); const [state, setState] = React.useState(""); const save = () => setHistory((h) => [...h, state]); const undo = () => { const prev = history[history.length - 1]; setHistory((h) => h.slice(0, -1)); setState(prev); }; return { state, setState, save, undo }; };
3.7 Template Method
Define the skeleton, defer specifics to children.
const BaseForm = ({ onSubmit, renderFields }) => ( <form onSubmit={onSubmit}> {renderFields()} <button type="submit">Submit</button> </form> ); const LoginForm = () => { const handleSubmit = (e) => { e.preventDefault(); console.log("Login"); }; return ( <BaseForm onSubmit={handleSubmit} renderFields={() => ( <> <input placeholder="Username" /> <input placeholder="Password" type="password" /> </> )} /> ); };
3.8 State
Allow an object to alter its behavior when its internal state changes.
const TrafficLight = () => { const [state, setState] = React.useState("red"); const next = { red: "green", green: "yellow", yellow: "red", }; return ( <button onClick={() => setState(next[state])}> Current: {state.toUpperCase()} </button> ); };
3.9 Visitor
Intent: Add new operations to components without changing their code.
Useful when you want to apply operations across a structure (e.g. analytics, transformations).
const Visitor = (component, fn) => { return React.cloneElement(component, fn(component.props)); }; const UserCard = (props) => <div>{props.name}</div>; // Apply visitor to change props without modifying UserCard itself const ModifiedUserCard = Visitor(<UserCard name="Alice" />, (props) => ({ name: props.name.toUpperCase() }));
3.10 Interpreter Pattern
Intent: Interpret or evaluate expressions defined in a mini-language.
Great for parsing user inputs, filters, or search queries.
const interpret = (expression) => { const tokens = expression.split(" "); const [a, op, b] = tokens; const numA = parseFloat(a); const numB = parseFloat(b); const operations = { '+': () => numA + numB, '-': () => numA - numB, '*': () => numA * numB, '/': () => numA / numB, }; return operations[op] ? operations[op]() : NaN; }; // interpret("10 * 2") => 20
3.11 Iterator Pattern
Intent: Traverse a collection without exposing its structure.
Useful in React for custom iteration logic (e.g. carousels, sliders, paginated lists)
const useIterator = (items = []) => { const [index, setIndex] = React.useState(0); const next = () => setIndex((i) => (i + 1) % items.length); const prev = () => setIndex((i) => (i - 1 + items.length) % items.length); const current = items[index]; return { current, next, prev, index }; }; // Usage: const Carousel = ({ images }) => { const { current, next, prev } = useIterator(images); return ( <div> <button onClick={prev}>Prev</button> <img src={current} alt="carousel" /> <button onClick={next}>Next</button> </div> ); };