9.3 KiB
Custom Hooks Documentation
This document provides comprehensive documentation for all custom hooks available in the project.
Overview
Custom hooks encapsulate reusable logic and patterns across components, improving code organization, maintainability, and consistency.
Available Hooks
useClickOutside
Detects clicks outside of specified elements. Useful for closing dropdowns, modals, or menus.
Location: app/hooks/useClickOutside.ts
Usage:
import { useClickOutside } from "../hooks";
const menuRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState(false);
useClickOutside([menuRef, buttonRef], () => setIsOpen(false), isOpen);
Parameters:
refs: Array of refs to elements that should not trigger the callbackhandler: Callback function to execute when clicking outsideenabled: Whether the hook is enabled (default: true)
Example: Used in Select.tsx for closing dropdown menus
useAnalytics
Centralized analytics tracking for component interactions. Supports both Google Analytics (gtag) and custom callbacks.
Location: app/hooks/useAnalytics.ts
Usage:
import { useAnalytics } from "../hooks";
const { trackEvent, trackCustomEvent } = useAnalytics();
// Standard event tracking
trackEvent({
event: "button_click",
category: "engagement",
label: "contact_button",
component: "AskOrganizer",
});
// Custom event with callback
trackCustomEvent(
"contact_button_click",
{
component: "AskOrganizer",
variant: "centered",
},
onContactClick, // Optional callback
);
Returns:
trackEvent: Function to track standard analytics eventstrackCustomEvent: Function to track custom events with optional callback
Example: Used in AskOrganizer.tsx for tracking button clicks
useComponentId
Generates unique component IDs for accessibility. Provides consistent ID generation pattern.
Location: app/hooks/useComponentId.ts
Usage:
import { useComponentId } from "../hooks";
const { id, labelId } = useComponentId("input", props.id);
// id: "input-123" or props.id if provided
// labelId: "input-123-label"
Parameters:
prefix: Prefix for the generated ID (e.g., "input", "select")providedId: Optional ID provided via props (takes precedence)
Returns:
id: Component IDlabelId: Associated label ID for accessibility
Example: Used in Input.tsx, TextArea.tsx, Checkbox.tsx
useFormField
Manages form field event handlers with disabled state handling. Ensures handlers respect disabled state.
Location: app/hooks/useFormField.ts
Usage:
import { useFormField } from "../hooks";
const { handleChange, handleFocus, handleBlur } = useFormField(disabled, {
onChange: (e) => setValue(e.target.value),
onFocus: (e) => setFocused(true),
onBlur: (e) => setFocused(false),
});
// Use in component
<input onChange={handleChange} onFocus={handleFocus} onBlur={handleBlur} />;
Parameters:
disabled: Whether the field is disabledhandlers: Object containing onChange, onFocus, onBlur handlers
Returns:
handleChange: Wrapped onChange handlerhandleFocus: Wrapped onFocus handlerhandleBlur: Wrapped onBlur handler
Example: Used in Input.tsx, TextArea.tsx
useComponentStyles
Manages component size and state styles. Provides a consistent pattern for styling components.
Location: app/hooks/useComponentStyles.ts
Usage:
import { useComponentStyles } from "../hooks";
const sizeStyles = {
small: { input: "h-8 text-xs", label: "text-xs" },
medium: { input: "h-10 text-sm", label: "text-sm" },
};
const stateStyles = {
default: { input: "border-gray-300", label: "text-gray-700" },
focus: { input: "border-blue-500", label: "text-gray-700" },
};
const { sizeClasses, stateClasses } = useComponentStyles({
size: "medium",
state: "default",
disabled: false,
error: false,
sizeStyles,
stateStyles,
});
Note: This hook is available but styling logic is often component-specific. Consider using it when you have multiple components with similar styling patterns.
useSchemaData
Generates Schema.org structured data (JSON-LD) for SEO and search engines.
Location: app/hooks/useSchemaData.ts
Usage:
import { useSchemaData } from "../hooks";
// HowTo schema
const schemaData = useSchemaData({
type: "HowTo",
name: "How to build a community",
description: "Step-by-step guide",
steps: [
{ name: "Step 1", text: "Start here" },
{ name: "Step 2", text: "Continue here" },
],
});
// Organization schema
const orgSchema = useSchemaData({
type: "Organization",
name: "Media Economies Design Lab",
url: "https://communityrule.com",
email: "medlab@colorado.edu",
sameAs: ["https://twitter.com/medlab"],
});
// Render in component
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
/>;
Supported Types:
Organization- Organization informationWebSite- Website navigation and searchHowTo- Step-by-step instructionsArticle- Blog posts and articlesBreadcrumbList- Navigation breadcrumbs
Example: Used in NumberedCards.tsx, Header.tsx, Footer.tsx
useMediaQuery
Responsive breakpoint detection using window.matchMedia.
Location: app/hooks/useMediaQuery.ts
Usage:
import { useMediaQuery, useIsMobile, useIsDesktop } from "../hooks";
// Using breakpoint key
const isMobile = useMediaQuery("lg", "max");
// Returns true if screen width < 1024px
// Using custom query
const isDesktop = useMediaQuery("(min-width: 1024px)");
// Convenience hooks
const isMobile = useIsMobile(); // Below lg breakpoint
const isDesktop = useIsDesktop(); // lg breakpoint and above
Available Breakpoints:
sm: 640pxmd: 768pxlg: 1024pxxl: 1280px2xl: 1536px
Example: Used in RelatedArticles.tsx for responsive behavior
useFormValidation
Form validation with field-level error handling.
Location: app/hooks/useFormValidation.ts
Usage:
import { useFormValidation, validationRules } from "../hooks";
const {
values,
errors,
touched,
handleChange,
handleBlur,
validate,
isValid,
reset,
} = useFormValidation({
initialValues: { email: "", password: "" },
validationRules: {
email: [validationRules.required, validationRules.email],
password: [validationRules.required, validationRules.minLength(8)],
},
validateOnChange: true,
validateOnBlur: true,
});
// In component
<input
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>;
{
errors.email && touched.email && <span>{errors.email}</span>;
}
Available Validation Rules:
validationRules.required- Field is requiredvalidationRules.email- Valid email formatvalidationRules.minLength(n)- Minimum lengthvalidationRules.maxLength(n)- Maximum lengthvalidationRules.pattern(regex, message)- Custom regex pattern
Returns:
values- Current form valueserrors- Field error messagestouched- Fields that have been interacted withhandleChange- Change handlerhandleBlur- Blur handlervalidate- Manual validation functionisValid- Boolean indicating if form is validreset- Reset form to initial valuessetValue- Programmatically set field value
Best Practices
-
Import from index: Always import hooks from
app/hooksindex file:import { useAnalytics, useComponentId } from "../hooks"; -
TypeScript: All hooks are fully typed. Use TypeScript for better IDE support.
-
Testing: Hooks should be tested independently. See
tests/unit/hooks/for examples. -
Documentation: When creating new hooks, add JSDoc comments and update this documentation.
-
Performance: Hooks use
useMemoanduseCallbackwhere appropriate to prevent unnecessary re-renders.
Refactored Components
The following components have been refactored to use custom hooks:
- Select - Uses
useClickOutside(now uses Container/Presentation pattern) - AskOrganizer - Uses
useAnalytics(now uses Container/Presentation pattern) - Input.tsx - Uses
useComponentIdanduseFormField - TextArea.tsx - Uses
useComponentIdanduseFormField - Checkbox.tsx - Uses
useComponentId - NumberedCards - Uses
useSchemaData(now uses Container/Presentation pattern) - RelatedArticles.tsx - Uses
useIsMobile
Note
: Components marked with "Container/Presentation pattern" have been refactored to separate logic (container) from presentation (view). Hooks are used in the container components. See Container/Presentation Pattern Guide for details.
Adding New Hooks
When creating a new hook:
- Create the hook file in
app/hooks/ - Export it from
app/hooks/index.ts - Add JSDoc comments with examples
- Write unit tests in
tests/unit/hooks/ - Update this documentation
- Refactor at least one component to use it as a proof of concept
Testing
All hooks have unit tests in tests/unit/hooks/. Run tests with:
npm test -- tests/unit/hooks
See Also
- React Hooks Documentation - Official React hooks documentation