Implement a Debounce Function
Create a debounce function with TypeScript types that properly handles the this context and cleanup.
Solution:
function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number,
immediate?: boolean
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return function executedFunction(this: any, ...args: Parameters<T>) {
const later = () => {
timeout = null;
if (!immediate) func.apply(this, args);
};
const callNow = immediate && !timeout;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(this, args);
};
}
// Usage
const debouncedSearch = debounce(function(this: any, query: string) {
console.log('Searching:', query);
}, 300);
// Cleanup method
function debounceWithCleanup<T extends (...args: any[]) => any>(
func: T,
wait: number
): T & { cancel: () => void } {
let timeout: NodeJS.Timeout | null = null;
const debounced = function(this: any, ...args: Parameters<T>) {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
} as T & { cancel: () => void };
debounced.cancel = () => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
return debounced;
}
Build a Custom useIntersectionObserver Hook
Implement a hook that tracks when an element enters/exits the viewport with proper TypeScript typing.
Solution:
function useIntersectionObserver(
options: IntersectionObserverInit = {}
): [React.RefObject<HTMLElement>, IntersectionObserverEntry | null] {
const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => setEntry(entry),
options
);
observer.observe(element);
return () => observer.disconnect();
}, [options]);
return [ref, entry];
}
// Usage
const MyComponent = () => {
const [ref, entry] = useIntersectionObserver({
threshold: 0.5,
rootMargin: '0px'
});
return (
<div ref={ref}>
{entry?.isIntersecting ? 'Visible' : 'Hidden'}
</div>
);
};
Create a Higher-Order Component
Build a HOC that adds loading and error states to any component that fetches data.
Solution:
interface WithDataFetchingProps {
loading: boolean;
error: Error | null;
data: any;
}
function withDataFetching<P extends object>(
WrappedComponent: React.ComponentType<P & WithDataFetchingProps>,
fetchData: () => Promise<any>
) {
return function WithDataFetchingComponent(props: P) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const [data, setData] = useState(null);
useEffect(() => {
fetchData()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <WrappedComponent {...props} loading={loading} error={error} data={data} />;
};
}
// Usage
const UserProfile = ({ data, loading, error }: { data: any } & WithDataFetchingProps) => (
<div>{data?.name}</div>
);
const UserProfileWithData = withDataFetching(
UserProfile,
() => fetch('/api/user').then(res => res.json())
);
Implement a Virtual Scroll List
Design a component that efficiently renders large lists by only rendering visible items.
Solution:
interface VirtualListProps<T> {
items: T[];
itemHeight: number;
containerHeight: number;
renderItem: (item: T, index: number) => React.ReactNode;
}
function VirtualList<T>({ items, itemHeight, containerHeight, renderItem }: VirtualListProps<T>) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const visibleStart = Math.floor(scrollTop / itemHeight);
const visibleEnd = Math.min(
visibleStart + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
const visibleItems = items.slice(visibleStart, visibleEnd);
const totalHeight = items.length * itemHeight;
const offsetY = visibleStart * itemHeight;
return (
<div
ref={containerRef}
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div key={visibleStart + index} style={{ height: itemHeight }}>
{renderItem(item, visibleStart + index)}
</div>
))}
</div>
</div>
</div>
);
}
// Usage
const LargeList = () => {
const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
return (
<VirtualList
items={items}
itemHeight={50}
containerHeight={400}
renderItem={(item) => <div>{item.name}</div>}
/>
);
};
Build a Form Builder
Create a dynamic form system with validation, conditional fields, and TypeScript type safety for form values.
Solution:
interface FormField {
name: string;
type: 'text' | 'email' | 'select' | 'checkbox';
label: string;
required?: boolean;
options?: string[];
condition?: (values: any) => boolean;
validation?: (value: any) => string | null;
}
interface FormBuilderProps {
fields: FormField[];
onSubmit: (values: any) => void;
}
function FormBuilder({ fields, onSubmit }: FormBuilderProps) {
const [values, setValues] = useState<Record<string, any>>({});
const [errors, setErrors] = useState<Record<string, string>>({});
const handleChange = (name: string, value: any) => {
setValues(prev => ({ ...prev, [name]: value }));
// Clear error when user starts typing
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
};
const validate = () => {
const newErrors: Record<string, string> = {};
fields.forEach(field => {
const value = values[field.name];
if (field.required && (!value || value === '')) {
newErrors[field.name] = `${field.label} is required`;
}
if (field.validation && value) {
const error = field.validation(value);
if (error) newErrors[field.name] = error;
}
});
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validate()) {
onSubmit(values);
}
};
const visibleFields = fields.filter(field =>
!field.condition || field.condition(values)
);
return (
<form onSubmit={handleSubmit}>
{visibleFields.map(field => (
<div key={field.name}>
<label>{field.label}</label>
{field.type === 'text' || field.type === 'email' ? (
<input
type={field.type}
value={values[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
/>
) : field.type === 'select' ? (
<select
value={values[field.name] || ''}
onChange={(e) => handleChange(field.name, e.target.value)}
>
<option value="">Select...</option>
{field.options?.map(option => (
<option key={option} value={option}>{option}</option>
))}
</select>
) : (
<input
type="checkbox"
checked={values[field.name] || false}
onChange={(e) => handleChange(field.name, e.target.checked)}
/>
)}
{errors[field.name] && <span style={{ color: 'red' }}>{errors[field.name]}</span>}
</div>
))}
<button type="submit">Submit</button>
</form>
);
}
// Usage
const MyForm = () => {
const fields: FormField[] = [
{ name: 'name', type: 'text', label: 'Name', required: true },
{ name: 'email', type: 'email', label: 'Email', required: true,
validation: (value) => !/\S+@\S+\.\S+/.test(value) ? 'Invalid email' : null },
{ name: 'country', type: 'select', label: 'Country', options: ['US', 'CA', 'UK'] },
{ name: 'subscribe', type: 'checkbox', label: 'Subscribe to newsletter' },
{ name: 'phone', type: 'text', label: 'Phone',
condition: (values) => values.country === 'US' }
];
return (
<FormBuilder
fields={fields}
onSubmit={(values) => console.log(values)}
/>
);
};