Coding Challenges

Hands-on coding challenges: debounce, custom hooks, HOCs, virtual scrolling, and form builders.

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)}
    />
  );
};