π React Design Patterns is a GitHub repository that explores best practices and reusable patterns for building scalable, maintainable, and efficient React applications. It covers essential patterns like Higher-Order Components (HOCs), Render Props, Compound Components, Custom Hooks, and more. Ideal for developers looking to improve their React architecture and write cleaner, more modular code.
An uncontrolled component in React is a form input element that manages its own state rather than relying on React state. This means that React does not control the value of the input field; instead, the DOM itself handles it.
import { useRef } from "react";
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
alert(`Entered value: ${inputRef.current.value}`);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
}
export default UncontrolledForm;
When to Use Uncontrolled Components?
- When integrating with third-party libraries that manipulate the DOM
- When you donβt need real-time control of input values
- When working with simple forms that donβt require state synchronization
- For more complex forms that need validation, default values, or conditional rendering, controlled components (using useState) are usually preferred.
In React, Controlled Components are those in which formβs data is handled by the componentβs state. It takes its current value through props and makes changes through callbacks like onClick, onChange, etc. A parent component manages its own state and passes the new values as props to the controlled component.
import React, { useEffect, useState } from "react";
export default function ControlledForm() {
const [error, setError] = useState("");
const [name, setName] = useState("");
const [age, setAge] = useState();
const handleSubmit = (e) => {
e.preventDefault();
console.log(name, age);
};
useEffect(() => {
console.log(name.length);
if (name.length < 1) {
setError(`name cannot be empty`);
} else {
setError("");
}
}, [name]);
return (
<form onSubmit={handleSubmit}>
{error && <p>{error}</p>}
<input
type="text"
name="name"
value={name}
placeholder="Name"
onChange={(e) => setName(e.target.value)}
/>
<input
type="number"
name="age"
value={age}
placeholder="Age"
onChange={(e) => setAge(e.target.value)}
/>
<button>Submit</button>
</form>
);
}
When to Use Controlled Components?
- When you need real-time updates (e.g., validation, formatting)
- When the input value depends on other state variables
- When handling dynamic forms
- When integrating with React state management (Redux, Context API, etc.)
For simple cases where React doesn't need to control the input, uncontrolled components (using ref) might be more efficient. π
in React, Higher-Order Components (HOCs) are a pattern used for reusing component logic. A HOC is a function that takes a component as an argument and returns a new enhanced component.
Example π
This is an example of a Higher-Order Component (HOC) that enhances a wrapped component with fetching, updating, and resetting resource data from an API
include-updatable-resources.jsx
import axios from "axios";
import { useEffect, useState } from "react";
const toCapitalCase = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
export const includeUpdatableResource = (
Component,
resourceUrl,
resourceName
) => {
return (props) => {
const [initialResource, setInitialResource] = useState(null);
const [resource, setResource] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
const data = response.data;
setInitialResource(data);
setResource(data);
})();
}, []);
const onChange = (updates) => {
setResource({ ...resource, ...updates });
};
const onPost = async () => {
const response = await axios.post(resourceUrl, {
[resourceName]: resource,
});
setInitialResource(response.data);
setResource(response.data);
};
const onReset = () => {
setResource(initialResource);
};
const resourceProps = {
[resourceName]: resource,
[`onChange${toCapitalCase(resourceName)}`]: onChange,
[`onPost${toCapitalCase(resourceName)}`]: onPost,
[`onReset${toCapitalCase(resourceName)}`]: onReset,
};
return <Component {...props} {...resourceProps} />;
};
};
usage π
user-form.jsx
import { includeUpdatableResource } from "./include-updatable-resource";
export const UserInfoForm = includeUpdatableResource(
({ user, onChangeUser, onPostUser, onResetUser }) => {
const { name, age, country } = user || {};
return user ? (
<>
<div className="">
<div>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => onChangeUser({ name: e.target.value })}
/>
</div>
<div>
<label htmlFor="age">Age</label>
<input
type="number"
id="age"
value={age}
onChange={(e) => onChangeUser({ age: Number(e.target.value) })}
/>
</div>
<button onClick={onResetUser}>Reset</button>
<button onClick={onPostUser}>Save</button>
</div>
</>
) : (
<h3>Loading...</h3>
);
},
"/users/2",
"user"
);
- β Encapsulation of Data Fetching Logic β No need to write fetching logic in every component.
- β Reusability β Can be used for any resource (e.g., posts, comments).
- β Enhances Components Dynamically β Without modifying them directly.
Custom hooks in React are reusable functions that encapsulate stateful logic and can be shared across multiple components. They allow you to extract common logic from components and reuse it, making your code more readable and maintainable.
A custom hook is simply a JavaScript function that follows the React hook naming convention, meaning it must start with use
, like useCustomHook()
. It can use built-in hooks like useState
, useEffect
, etc.
The primary goal of custom hooks is to encapsulate and reuse complex logic across multiple components. This helps in:
β
Code Reusability β Avoid repeating the same logic in multiple components.
β
Separation of Concerns β Keep UI components clean by moving logic to hooks.
β
Better Readability & Maintainability β Easier to understand and manage.
β
Avoiding Component Bloat β Components remain focused on rendering, not handling logic.
β
Easy Testing β Custom hooks can be tested independently.
Instead of writing API fetching logic in multiple components, you can extract it into a custom hook.
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) throw new Error("Error fetching data");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
import React from "react";
import useFetch from "./useFetch";
function UsersList() {
const { data, loading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UsersList;
Use custom hooks when:
- You need to reuse logic across multiple components.
- Your component is getting too complex with state and effects.
- You want to abstract API calls, authentication logic, form handling, etc.
πΉ Custom hooks extract and reuse logic across multiple components.
πΉ They must start with "use" and can use other hooks inside.
πΉ Help with reusability, separation of concerns, and maintainability.
πΉ Example: useFetch()
for API requests, useLocalStorage()
for storage, useDarkMode()
for themes, etc.
This document explains how recursive components work in React and provides an example of a recursive component used to display nested comments.
A recursive component is a React component that calls itself until a base condition is met. This is useful for handling tree-like structures such as:
- Nested comments
- File directories
- Menus with submenus
const comments = [
{
id: 1,
text: "This is a comment",
replies: [
{
id: 2,
text: "This is a nested reply",
replies: [{ id: 3, text: "A deeper nested reply", replies: [] }],
},
],
},
];
Each comment has a replies
field, which contains an array of more comments.
const Comment = ({ comment }) => {
return (
<div
style={{
marginLeft: "20px",
borderLeft: "1px solid gray",
paddingLeft: "10px",
}}
>
<p>{comment.text}</p>
{comment.replies.length > 0 && (
<div>
{comment.replies.map((reply) => (
<Comment key={reply.id} comment={reply} />
))}
</div>
)}
</div>
);
};
const CommentSection = ({ comments }) => {
return (
<div>
{comments.map((comment) => (
<Comment key={comment.id} comment={comment} />
))}
</div>
);
};
// Usage:
export default function App() {
return <CommentSection comments={comments} />;
}
- Base Case: If a comment has no
replies
, the recursion stops. - Recursive Case: If a comment has replies, the
<Comment>
component calls itself. - Each level of recursion adds indentation, showing a nested hierarchy.
This is a comment
βββ This is a nested reply
βββ A deeper nested reply
Each recursive <Comment>
component renders itself inside itself for replies.
β When dealing with nested data (e.g., JSON structures, tree-like lists). β When components have self-similar behavior at different levels. β When a fixed iteration wonβt work (deeply nested structures).
β If the data is not deeply nested, .map()
might be enough.
β If recursion causes too many re-renders, impacting performance.
β If state updates need tracking (can become complex).
This project is licensed under the MIT License.
This document explains how composition works in React and provides an example of using composition to structure components efficiently.
Component composition is the practice of combining smaller components to build a larger, reusable UI. Instead of using inheritance, React encourages composition to keep components flexible and maintainable.
- Encapsulation β Each component handles its own logic.
- Reusability β Components can be reused in different contexts.
- Flexibility β Easily swap or extend functionalities without modifying existing code.
const Header = () => (
<header>
<h1>My App</h1>
</header>
);
const Footer = () => (
<footer>
<p>Β© 2024 My App</p>
</footer>
);
const Layout = ({ children }) => {
return (
<div style={{ border: "1px solid gray", padding: "10px" }}>
{children} {/* Allows any component to be inserted dynamically */}
</div>
);
};
const HomePage = () => {
return (
<Layout>
<Header />
<main>
<p>Welcome to my app!</p>
</main>
<Footer />
</Layout>
);
};
Layout
acts as a wrapper component, controlling the overall structure.- The
{children}
prop allows dynamic content, making the layout reusable. Header
,main
, andFooter
are injected inside theLayout
.
Composition also allows passing components as props to modify behavior dynamically.
const Card = ({ title, content, FooterComponent }) => {
return (
<div style={{ border: "1px solid black", padding: "10px", margin: "5px" }}>
<h2>{title}</h2>
<p>{content}</p>
{FooterComponent && <FooterComponent />}{" "}
{/* Dynamically insert a component */}
</div>
);
};
const SimpleFooter = () => <p>Simple Footer</p>;
const DetailedFooter = () => <p>More details here...</p>;
const App = () => (
<div>
<Card
title="Card 1"
content="This is a basic card."
FooterComponent={SimpleFooter}
/>
<Card
title="Card 2"
content="This is another card."
FooterComponent={DetailedFooter}
/>
</div>
);
β
When structuring layouts (e.g., headers, sidebars, footers).
β
When passing dynamic UI as props (e.g., modals, cards, and lists).
β
When creating highly reusable components.
β If a component is simple and isolated, no need for composition.
β If passing too many components makes it complex to manage.
Partial components are smaller subcomponents within a larger component that handle a specific part of the UI. This helps in breaking down complex components into smaller, reusable pieces.
- β Encapsulation β Each component handles a specific UI section.
- β Reusability β Components can be reused in different contexts.
- β Scalability β UI updates only require changes in specific components.
export const partialComponent = (Component, partialProps) => {
return (props) => {
return <Component {...props} {...partialProps} />;
};
};
export const Button = ({ size, text, color, ...props }) => {
return (
<button
style={{
fontSize: size === "small" ? "10px" : "32px",
backgroundColor: color,
}}
>
{text}
</button>
);
};
export const RedButton = partialComponent(Button, { color: "red" });
export const SmallRedButton = partialComponent(RedButton, { size: "small" });
export const SmallGreenButton = partialComponent(Button, {
color: "green",
size: "small",
});
const App = () => {
return (
<div>
<SmallRedButton text="I am a small red button" />
</div>
);
};
- Encapsulation β Each component handles only one part of the UI (
CardHeader
,CardBody
,CardFooter
). - Reusability β The same partial components can be reused in different contexts.
- Scalability β If the UI changes, we only need to update individual partial components.
β
When a component has distinct sections (e.g., headers, footers, lists).
β
When multiple components share a similar structure.
β
When you want to improve readability in large components.
β Avoid Over-Fracturing β If a component is too simple, breaking it down may add unnecessary complexity.
In simple terms:
One thing changes β other things get notified and react to it.
Like:
- Your phone gets a message β it vibrates, shows a notification, and plays a sound.
- You're "observing" new messages.
React components can "observe" events or state. When something changes (like a user clicking a button), observers (components) get updated.
We can do this with event emitters like mitt
.
Letβs say:
- One component emits a message (
ButtonComponent
) - Another component observes that message (
DisplayComponent
)
Install it:
npm install mitt
Create an event bus:
// eventBus.js
import mitt from "mitt";
const emitter = mitt();
export default emitter;
// ButtonComponent.jsx
import emitter from "./eventBus";
function ButtonComponent() {
const sendMessage = () => {
emitter.emit("message", "Hello from the button!");
};
return <button onClick={sendMessage}>Send Message</button>;
}
export default ButtonComponent;
// DisplayComponent.jsx
import { useEffect, useState } from "react";
import emitter from "./eventBus";
function DisplayComponent() {
const [message, setMessage] = useState("No messages yet.");
useEffect(() => {
const handler = (msg) => {
setMessage(msg);
};
emitter.on("message", handler);
// Clean up on unmount
return () => {
emitter.off("message", handler);
};
}, []);
return <div>π© Message: {message}</div>;
}
export default DisplayComponent;
// App.jsx
import ButtonComponent from "./ButtonComponent";
import DisplayComponent from "./DisplayComponent";
function App() {
return (
<div>
<h1>Observer Pattern with mitt</h1>
<ButtonComponent />
<DisplayComponent />
</div>
);
}
export default App;
- You press a button (emit event)
- Another thing is listening (observer)
- It reacts and updates
This is the Observer Pattern in action β and mitt
makes it clean and simple.
Want me to expand this with multiple events or build a chat-like example?