// components/DebugDataViewer.jsx /** * Debug Data Viewer Component * * Fetches data from a specified API endpoint and displays it as formatted JSON. * Includes a button to toggle visibility and supports optional automatic polling. * WARNING: Use with caution, especially with sensitive data or in production. */ function DebugDataViewer({ apiEndpoint, // API URL to fetch data from buttonText, // Text for the show/hide button title, // Optional heading for the viewer section pollingInterval = 0 // Interval in ms for auto-refresh (0 disables polling) }) { // --- State Management --- const [data, setData] = React.useState(null); // Stores fetched data as JSON string const [isLoading, setIsLoading] = React.useState(false); // Loading indicator state const [error, setError] = React.useState(null); // Error message state const [showData, setShowData] = React.useState(false); // Visibility toggle state // Ref to store the polling interval ID for cleanup const intervalRef = React.useRef(null); // --- Data Fetching --- // Memoized function to fetch data from the API endpoint const fetchData = React.useCallback(() => { // Don't necessarily show loading indicator for background polls // setError(null); // Optionally clear error before each attempt fetch(apiEndpoint) .then((response) => { // Handle HTTP errors if (!response.ok) { return response.json().then(errData => { throw new Error(errData.error || `HTTP error! status: ${response.status}`); }).catch(() => { throw new Error(`HTTP error! status: ${response.status}`); }); } return response.json(); // Parse JSON body }) .then((fetchedData) => { // Handle application-level errors within the JSON response if (!fetchedData.success) { // Check for success flag throw new Error(fetchedData.error || 'API request failed.'); } // Format the actual data payload as JSON string const newDataString = JSON.stringify(fetchedData.data, null, 2); // Format data part // Only update state if data changed to prevent needless re-renders if (newDataString !== data) { setData(newDataString); } setError(null); // Clear error on success }) .catch((fetchError) => { // Handle fetch or API errors console.error(`Error fetching debug data from ${apiEndpoint}:`, fetchError); // Avoid constantly flashing errors during polling if (!error) { // Only set error if not already showing one setError(`Failed to load data. (${fetchError.message})`); } // Keep potentially stale data visible on polling error // setData(null); }) .finally(() => { // Ensure loading state is off after fetch attempt setIsLoading(false); }); // Dependencies for useCallback }, [apiEndpoint, data, error]); // --- Polling Logic --- // Effect to manage the polling interval lifecycle React.useEffect(() => { // Helper function to clear the interval const clearPollingInterval = () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; // Clear any previous interval before setting a new one clearPollingInterval(); // Start polling if viewer is visible and interval is valid if (showData && pollingInterval > 0) { intervalRef.current = setInterval(fetchData, pollingInterval); } // Cleanup function: Clear interval on unmount or dependency change return clearPollingInterval; // Dependencies: Re-run effect if visibility, interval, or endpoint changes }, [showData, pollingInterval, apiEndpoint, fetchData]); // --- Event Handlers --- // Handle clicks on the show/hide button const handleButtonClick = () => { const willShow = !showData; // Determine the next visibility state setShowData(willShow); // Update visibility state // If showing the data, trigger an immediate fetch with loading indicator if (willShow) { setIsLoading(true); fetchData(); } // Note: Interval cleanup/setup is handled by the useEffect hook }; // --- Render Component UI --- return (
{/* Optional Title */} {title &&

{title}

} {/* Show/Hide Button */} {/* Conditionally render the data display area */} {showData && (
{/* Display error message if fetch failed */} {error &&
{error}
} {/* Display fetched data formatted as JSON */} {data && !error &&
{data}
} {/* Display message if loading finished with no data/error */} {!isLoading && !error && !data &&

No data to display or data is empty.

}
)}
); } // --- Prop Type Validation (Development Only) --- DebugDataViewer.propTypes = { apiEndpoint: PropTypes.string.isRequired, buttonText: PropTypes.string.isRequired, title: PropTypes.string, pollingInterval: PropTypes.number, // Optional, defaults to 0 (disabled) };