vkashti / hooks / useSyncedDate.ts
useSyncedDate.ts
Raw
import { useState, useCallback, useEffect, ReactNode, createElement, Fragment } from 'react';
import { useSearchParams } from 'next/navigation';

function parseLocalDate(dateStr: string): Date | null {
  const [year, month, day] = dateStr.split('-');
  if (!year || !month || !day) return null;
  return new Date(Number(year), Number(month) - 1, Number(day), 12, 0, 0);
}

function formatLocalDate(date: Date): string {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  return `${y}-${m}-${d}`;
}

/**
 * Hook that handles date synchronization with URL parameters
 * Should only be used within a component wrapped in Suspense
 */
export function useSyncedDateWithParams() {
  const searchParams = useSearchParams();
  
  // Initialize with null, then set in useEffect to ensure client-side only
  const [date, setDate] = useState<Date | null>(null);
  
  // Get date from search params
  useEffect(() => {
    const dateStr = searchParams?.get('date') || null;
    const parsedDate = dateStr ? parseLocalDate(dateStr) : null;
    const validDate = parsedDate && !isNaN(parsedDate.getTime()) ? parsedDate : new Date();
    setDate(validDate);
  }, [searchParams]);

  const handleDateChange = useCallback((newDate: Date | null) => {
    setDate(newDate);
    
    // Update URL
    if (typeof window !== 'undefined') {
      const params = new URLSearchParams(window.location.search);
      if (newDate) {
        params.set('date', formatLocalDate(newDate));
      } else {
        params.delete('date');
      }
      window.history.replaceState({}, '', `${window.location.pathname}?${params}`);
    }
  }, []);

  return { date, handleDateChange };
}

/**
 * Client component that safely uses useSearchParams
 * Must be wrapped in a Suspense boundary
 */
export function DateParamHandler({ children }: { 
  children: (props: { date: Date | null; handleDateChange: (newDate: Date | null) => void }) => ReactNode 
}) {
  const { date, handleDateChange } = useSyncedDateWithParams();
  
  // Use React.createElement to avoid JSX syntax issues
  return createElement(Fragment, null, children({ date, handleDateChange }));
}

/**
 * @deprecated Use DateParamHandler with Suspense instead
 */
export function useSyncedDate() {
  const searchParams = useSearchParams();
  
  // Initialize with null, then set in useEffect to ensure client-side only
  const [date, setDate] = useState<Date | null>(null);
  
  // Get date from search params
  useEffect(() => {
    const dateStr = searchParams?.get('date') || null;
    const parsedDate = dateStr ? parseLocalDate(dateStr) : null;
    const validDate = parsedDate && !isNaN(parsedDate.getTime()) ? parsedDate : new Date();
    setDate(validDate);
  }, [searchParams]);

  const handleDateChange = useCallback((newDate: Date | null) => {
    setDate(newDate);
    
    // Update URL
    if (typeof window !== 'undefined') {
      const params = new URLSearchParams(window.location.search);
      if (newDate) {
        params.set('date', formatLocalDate(newDate));
      } else {
        params.delete('date');
      }
      window.history.replaceState({}, '', `${window.location.pathname}?${params}`);
    }
  }, []);

  return [date, handleDateChange] as const;
}