Advanced: User Preferences
Relevant source files
The following files were used as context for generating this wiki page:The QR-Tool application enhances user experience by persisting UI settings and choices locally on the client's machine. This system is built around the browser's localStorage API, allowing users to retain their customizations across sessions. All preference management is handled client-side, ensuring that no user data is sent to a server.
The core of this feature is the useLocalStorage custom React hook, which abstracts the logic for reading from and writing to localStorage. The application stores various UI-related states, such as theme (dark/light mode), QR code generation options, and selected UI mode ('Quick' vs. 'Pro'). Importantly, the system is designed to only store non-sensitive UI preferences, a practice verified in the project's security audit.
Sources: README.md, src/App.jsx:254-272, SECURITY_AUDIT_REPORT.md
Persistence Mechanism
User preferences are persisted using the browser's localStorage API. A custom React hook, useLocalStorage, is implemented within App.jsx to manage this interaction.
Sources: src/App.jsx:254-272
The useLocalStorage Hook
This hook synchronizes a React state variable with a localStorage key. It initializes the state with the value from localStorage if it exists, or with a provided initial value otherwise. Any subsequent changes to the state are automatically saved back to localStorage.
The hook includes error handling to prevent crashes if localStorage is unavailable or full. It also features a disabled flag (named privateMode in App.jsx) which, when true, prevents any interaction with localStorage, causing the hook to behave like a standard useState.
Sources: src/App.jsx:254-272
The following diagram illustrates the data flow of the useLocalStorage hook.
Sources: src/App.jsx:254-272
privateMode
A privateMode flag is defined in App.jsx and passed to every useLocalStorage call. When set to true, it disables all localStorage operations, effectively making the session ephemeral without persisting any settings.
// src/App.jsx:277
const privateMode = true;
// src/App.jsx:281
const [qrType, setQrType] = useLocalStorage('qr_type', QR_TYPES.URL, privateMode);
Sources: src/App.jsx:277, 281
Stored Preferences
The application stores a variety of settings in localStorage to maintain the user's configuration. The table below details each key, its purpose, and its default value.
| Key | Purpose | Default Value | Source Location |
|---|---|---|---|
theme | Stores the current UI theme. | 'light' | index.html:9-14 |
qr_type | The selected type of QR code (URL, Text, etc.). | QR_TYPES.URL ('url') | src/App.jsx:281 |
ui_mode | The selected user interface mode. | 'quick' | src/App.jsx:346 |
qr_size | The pixel size of the QR code. | 360 | src/App.jsx:347 |
qr_size_preset | The ID of the selected size preset. | 'web' | src/App.jsx:348 |
qr_ecc_mode | The Error Correction Code (ECC) level. | 'AUTO' | src/App.jsx:349 |
qr_margin | Whether to include the quiet zone (margin). | true | src/App.jsx:350 |
qr_transparent_bg | Whether the PNG background is transparent. | false | src/App.jsx:351 |
qr_fg | The foreground (QR) color in hex format. | '#000000' | src/App.jsx:353 |
qr_bg | The background color in hex format. | '#FFFFFF' | src/App.jsx:354 |
qr_fmt | The export format ('canvas' for PNG/JPG, 'svg'). | 'canvas' | src/App.jsx:358 |
qr_logo_mode | The automatic or manual mode for logo sizing. | 'auto' | src/App.jsx:360 |
qr_logo_custom | The custom scale for the logo (0-1). | 0.2 | src/App.jsx:361 |
Sources: src/App.jsx:281-361, index.html:9-14, SECURITY_AUDIT_REPORT.md
Theme (Dark/Light Mode)
The application supports both dark and light themes, and the user's choice is persisted. The implementation is designed to prevent a "flash of unstyled content" (FOUC) on page load.
Initial Theme Loading
An inline script in index.html runs before the React application mounts. This script reads the theme key from localStorage and applies the corresponding dark class to the <html> element immediately. If no theme is saved, it defaults to 'light'.
<!-- index.html:8-15 -->
<script>
// Khởi tạo theme TRƯỚC KHI React load
const saved = localStorage.getItem('theme');
const initial = saved || 'light';
const isDark = initial === 'dark';
document.documentElement.classList.toggle('dark', isDark);
document.documentElement.dataset.theme = initial;
if (!saved) localStorage.setItem('theme', initial);
</script>
Sources: index.html:8-15
Theme Management Flow
The useTheme hook (consumed in App.jsx) manages the theme state within the React application. When the user toggles the theme, the hook updates the state, which in turn updates the localStorage value and the class on the <html> element.
The sequence diagram below shows the entire process from initial load to a user-initiated theme change.
Sources: index.html:8-15, src/App.jsx:276, 610-623
Resetting Preferences
The application provides a "Reset" button that reverts all QR code settings to their default values. This is handled by the handleReset function in App.jsx.
The function works by calling the state setter function for each preference, providing the original default value. Because these states are managed by the useLocalStorage hook, this action subsequently triggers useEffect within each hook, causing the localStorage keys to be updated with the default values. It does not directly call localStorage.clear().
// src/App.jsx:558-588
const handleReset = () => {
if (confirm('Đặt lại tất cả về mặc định?')) {
setQrType(QR_TYPES.URL);
setQrData({
url: 'https://example.com',
// ... other qrData fields
});
setSize(360);
setSelectedPreset('web');
setEccMode('AUTO');
setIncludeMargin(true);
setTransparentBg(false);
handleFgChange('#000000');
handleBgChange('#FFFFFF');
setFormat('canvas');
setLogoMode('auto');
revokeIfBlob(logoUrl);
setLogoUrl('');
}
};
Sources: src/App.jsx:558-588