Examples
Real-world examples and usage patterns for OpenHooks in different scenarios.
Examples
Discover how to use OpenHooks in real-world scenarios with these practical examples.
🎯 Common Use Cases
Modal Components
Building accessible modals with click-outside behavior
Search Interfaces
Debounced search with local storage persistence
Mobile-First Design
Responsive components that adapt to device type
Form Handling
Enhanced forms with validation and persistence
🎭 Modal Components
Basic Modal with Click Outside
import { useRef, useState } from "react";
import { useClickOutside } from "./hooks/useClickOutside";
function Modal({ isOpen, onClose, children }) {
const modalRef = useRef(null);
useClickOutside(modalRef, onClose);
if (!isOpen) return null;
return (
<div className="modal-overlay">
<div ref={modalRef} className="modal-content">
<button onClick={onClose} className="close-button">
×
</button>
{children}
</div>
</div>
);
}
// Usage
function App() {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div>
<button onClick={() => setIsModalOpen(true)}>Open Modal</button>
<Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
<h2>Modal Content</h2>
<p>Click outside to close!</p>
</Modal>
</div>
);
}Advanced Modal with Keyboard Support
import { useRef, useState, useEffect } from "react";
import { useClickOutside } from "./hooks/useClickOutside";
function AdvancedModal({ isOpen, onClose, children }) {
const modalRef = useRef(null);
useClickOutside(modalRef, onClose);
// Add keyboard support
useEffect(() => {
const handleEscape = (e) => {
if (e.key === "Escape") onClose();
};
if (isOpen) {
document.addEventListener("keydown", handleEscape);
// Focus trap
modalRef.current?.focus();
}
return () => document.removeEventListener("keydown", handleEscape);
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div className="modal-overlay" role="dialog" aria-modal="true">
<div ref={modalRef} className="modal-content" tabIndex={-1}>
{children}
</div>
</div>
);
}🔍 Search Interfaces
Debounced Search with Persistence
import { useState, useEffect } from "react";
import { useDebounce } from "./hooks/useDebounce";
import { useLocalStorage } from "./hooks/useLocalStorage";
function SearchInterface() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [recentSearches, setRecentSearches] = useLocalStorage(
"recent-searches",
[]
);
const debouncedQuery = useDebounce(query, 300);
// Perform search when debounced query changes
useEffect(() => {
if (debouncedQuery.trim()) {
performSearch(debouncedQuery);
// Add to recent searches
setRecentSearches((prev) => [
debouncedQuery,
...prev.filter((term) => term !== debouncedQuery).slice(0, 4),
]);
}
}, [debouncedQuery]);
const performSearch = async (searchTerm) => {
try {
const response = await fetch(`/api/search?q=${searchTerm}`);
const data = await response.json();
setResults(data.results);
} catch (error) {
console.error("Search failed:", error);
}
};
return (
<div className="search-container">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
className="search-input"
/>
{/* Recent searches */}
{!query && recentSearches.length > 0 && (
<div className="recent-searches">
<h3>Recent Searches</h3>
{recentSearches.map((term, index) => (
<button
key={index}
onClick={() => setQuery(term)}
className="recent-search-item"
>
{term}
</button>
))}
</div>
)}
{/* Search results */}
{results.length > 0 && (
<div className="search-results">
{results.map((result) => (
<div key={result.id} className="search-result">
<h4>{result.title}</h4>
<p>{result.description}</p>
</div>
))}
</div>
)}
</div>
);
}📱 Mobile-First Design
Responsive Navigation
import { useState } from "react";
import { useMobile } from "./hooks/useMobile";
import { useClickOutside } from "./hooks/useClickOutside";
function ResponsiveNavigation() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const isMobile = useMobile();
const menuRef = useRef(null);
useClickOutside(menuRef, () => {
if (isMobile) setIsMenuOpen(false);
});
return (
<nav className="navigation">
<div className="nav-brand">
<h1>My App</h1>
</div>
{isMobile ? (
// Mobile navigation
<>
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="menu-toggle"
>
☰
</button>
{isMenuOpen && (
<div ref={menuRef} className="mobile-menu">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
)}
</>
) : (
// Desktop navigation
<div className="desktop-menu">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
)}
</nav>
);
}Adaptive Content Layout
import { useMobile } from "./hooks/useMobile";
function AdaptiveLayout({ children }) {
const isMobile = useMobile();
return (
<div className={`layout ${isMobile ? "mobile" : "desktop"}`}>
<header className="header">
<Navigation />
</header>
<main className="main-content">
{isMobile ? (
// Mobile-optimized layout
<div className="mobile-stack">{children}</div>
) : (
// Desktop layout with sidebar
<div className="desktop-grid">
<aside className="sidebar">
<Sidebar />
</aside>
<div className="content">{children}</div>
</div>
)}
</main>
</div>
);
}📝 Form Handling
Persistent Form with Auto-save
import { useState, useEffect } from "react";
import { useLocalStorage } from "./hooks/useLocalStorage";
import { useDebounce } from "./hooks/useDebounce";
function PersistentForm() {
const [formData, setFormData] = useLocalStorage("form-draft", {
name: "",
email: "",
message: "",
});
const [isDraft, setIsDraft] = useState(false);
const debouncedFormData = useDebounce(formData, 1000);
// Auto-save draft
useEffect(() => {
if (
debouncedFormData.name ||
debouncedFormData.email ||
debouncedFormData.message
) {
setIsDraft(true);
console.log("Draft saved automatically");
}
}, [debouncedFormData]);
const handleInputChange = (field, value) => {
setFormData((prev) => ({
...prev,
[field]: value,
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
await submitForm(formData);
// Clear draft after successful submission
setFormData({ name: "", email: "", message: "" });
setIsDraft(false);
alert("Form submitted successfully!");
} catch (error) {
alert("Submission failed. Your draft has been saved.");
}
};
const clearDraft = () => {
setFormData({ name: "", email: "", message: "" });
setIsDraft(false);
};
return (
<form onSubmit={handleSubmit} className="persistent-form">
{isDraft && (
<div className="draft-notice">
📝 Draft saved automatically
<button type="button" onClick={clearDraft}>
Clear Draft
</button>
</div>
)}
<input
type="text"
value={formData.name}
onChange={(e) => handleInputChange("name", e.target.value)}
placeholder="Your Name"
/>
<input
type="email"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
placeholder="Your Email"
/>
<textarea
value={formData.message}
onChange={(e) => handleInputChange("message", e.target.value)}
placeholder="Your Message"
rows={5}
/>
<button type="submit">Submit Form</button>
</form>
);
}🎮 Interactive Components
Copy-to-Clipboard Button
import { useClipboard } from "./hooks/useClipboard";
function CopyButton({ text, children }) {
const { copied, copyToClipboard } = useClipboard();
return (
<button
onClick={() => copyToClipboard(text)}
className={`copy-button ${copied ? "copied" : ""}`}
>
{copied ? "✅ Copied!" : children}
</button>
);
}
// Usage
function CodeBlock({ code }) {
return (
<div className="code-block">
<pre>{code}</pre>
<CopyButton text={code}>📋 Copy Code</CopyButton>
</div>
);
}🔧 Integration Tips
Combine Multiple Hooks: Most real-world components benefit from combining
multiple hooks. For example, a search component might use useDebounce,
useLocalStorage, and useClickOutside together.
Performance: Remember to use useCallback and useMemo when passing
callbacks to OpenHooks to prevent unnecessary re-renders.
Accessibility: Always consider accessibility when using hooks like
useClickOutside. Provide keyboard alternatives and proper ARIA attributes.
📚 Next Steps
Ready to implement these patterns in your project?