Skip to main content

Handling Personal Data: GDPR and React Privacy

GDPR (General Data Protection Regulation) and similar privacy laws (CCPA, LGPD, ePrivacy) require applications to be transparent about data collection, obtain explicit user consent, and honor deletion requests. React applications that collect personal information—email, name, location, browsing behavior—must implement consent mechanisms, privacy notices, and data deletion endpoints. Violations result in fines up to 4% of annual revenue (GDPR) or $7,500 per violation (CCPA). This guide covers how to implement GDPR-compliant consent banners in React, store PII safely, delete data on request, and avoid common compliance pitfalls.

What Counts as Personal Data

Personal data (PII) under GDPR includes any information that identifies an individual, directly or indirectly:

  • Identifiers: Name, email address, phone number, username, ID number
  • Location data: IP address, GPS coordinates, device identifiers
  • Online identifiers: Cookies, browser fingerprints, email hashes
  • Physiological data: Biometric data, genetic data
  • Behavioral data: Browsing history, purchase history, tracking pixels
  • Financial data: Bank account, payment card information (also PCI-DSS regulated)
  • Health data: Medical records, vaccine status (sensitive category)

If your React app collects any of this information, you must comply with GDPR. If you don't collect it but use third-party services (analytics, ads, session replay) that do, you are still responsible—you are a "data controller" or "data processor" depending on your contract.

GDPR requires "explicit consent" before collecting non-essential data. You must ask users before loading analytics, ads, or session replay scripts. A simple approach:

import React, { useEffect, useState } from "react";

function ConsentBanner() {
const [showBanner, setShowBanner] = useState(false);
const [preferences, setPreferences] = useState({
analytics: false,
marketing: false,
necessary: true // Always required
});

useEffect(() => {
// Check if user has already consented
const stored = localStorage.getItem("consentPreferences");
if (!stored) {
setShowBanner(true);
} else {
const prefs = JSON.parse(stored);
setPreferences(prefs);
applyConsent(prefs);
}
}, []);

function applyConsent(prefs) {
// Load analytics only if consented
if (prefs.analytics) {
loadGoogleAnalytics();
loadSentry();
}

// Load ads/marketing tags only if consented
if (prefs.marketing) {
loadFacebookPixel();
}

// Always load necessary cookies (session management, CSRF protection)
// These don't require consent
}

function handleAcceptAll() {
const newPrefs = { necessary: true, analytics: true, marketing: true };
setPreferences(newPrefs);
localStorage.setItem("consentPreferences", JSON.stringify(newPrefs));
applyConsent(newPrefs);
setShowBanner(false);
}

function handleRejectNonEssential() {
const newPrefs = { necessary: true, analytics: false, marketing: false };
setPreferences(newPrefs);
localStorage.setItem("consentPreferences", JSON.stringify(newPrefs));
applyConsent(newPrefs);
setShowBanner(false);
}

function handleManagePreferences() {
// Show detailed preference modal
setShowBanner(true);
}

if (!showBanner) return null;

return (
div className="consent-banner" style={{ position: "fixed", bottom: 0, width: "100%", backgroundColor: "#1a1a1a", color: "white", padding: "20px", zIndex: 9999 }}>
<h3>Your Privacy Matters</h3>
<p>We use cookies and analytics to improve your experience. See our Privacy Policy for details.</p>
<div className="consent-actions">
<button onClick={handleRejectNonEssential}>Reject Non-Essential</button>
<button onClick={handleManagePreferences}>Manage Preferences</button>
<button onClick={handleAcceptAll} style={{ fontWeight: "bold" }}>Accept All</button>
</div>
/div>
);
}

export default ConsentBanner;

Key points:

  • Obtain consent before loading tracking scripts
  • Offer granular choices (analytics, marketing, necessary)
  • Store preferences in localStorage so you don't ask again
  • Link to your Privacy Policy
  • Provide an easy way to manage preferences later

Safe Storage of PII in React

Never store sensitive PII in localStorage—it is accessible to any JavaScript on the page (including XSS attacks). Instead:

// UNSAFE: Never store sensitive PII in localStorage
localStorage.setItem("user", JSON.stringify({
name: "John Doe",
email: "[email protected]",
ssn: "123-45-6789" // Major security violation
}));

// Safe: Store only non-sensitive identifiers
localStorage.setItem("userId", user.id);
localStorage.setItem("userEmail", user.email); // Email is less sensitive than SSN

// Sensitive data (password, payment info) should never be stored client-side

For sensitive data, use your backend session mechanism (httpOnly cookies). An httpOnly cookie cannot be accessed by JavaScript, protecting it from XSS:

// Backend (Express)
app.post("/login", async (req, res) => {
const user = await authenticateUser(req.body.email, req.body.password);

// Set httpOnly cookie (automatically sent with every request, cannot be accessed by JS)
res.cookie("sessionToken", generateToken(user.id), {
httpOnly: true, // Inaccessible to JavaScript
secure: true, // HTTPS only
sameSite: "strict" // CSRF protection
});

res.json({ userId: user.id }); // Don't send the token itself
});

Implementing User Data Deletion (Right to be Forgotten)

GDPR's "right to be forgotten" requires you to delete user data on request. Implement a deletion endpoint:

// React component
function AccountSettings() {
const [deleting, setDeleting] = useState(false);

async function handleDeleteAccount() {
const confirmed = window.confirm(
"This will permanently delete your account and all associated data. This cannot be undone."
);

if (!confirmed) return;

setDeleting(true);

try {
const response = await fetch("/api/account/delete", {
method: "DELETE",
headers: { "Content-Type": "application/json" }
});

if (response.ok) {
localStorage.clear();
window.location.href = "/account-deleted";
} else {
alert("Failed to delete account");
}
} finally {
setDeleting(false);
}
}

return (
div className="account-settings">
<h2>Account Settings</h2>
<button onClick={handleDeleteAccount} disabled={deleting}>
{deleting ? "Deleting..." : "Delete My Account and Data"}
</button>
/div>
);
}

Your backend deletes the user and all associated records:

// Backend
app.delete("/api/account/delete", authenticateUser, async (req, res) => {
const userId = req.user.id;

// Delete user record
await User.deleteOne({ id: userId });

// Delete associated data in other tables
await UserProfile.deleteMany({ userId });
await UserConsent.deleteMany({ userId });
await AuditLog.deleteMany({ userId });

// Delete from third-party services
await deleteFromAnalytics(userId);
await deleteFromCRM(userId);

// Log the deletion for audit compliance
auditLog(`User ${userId} deleted their account at ${new Date().toISOString()}`);

res.status(204).send();
});

Privacy Policy and Data Processing Transparency

Your application must have a clear Privacy Policy that explains:

  1. What data you collect (forms, analytics, cookies, location)
  2. Why you collect it (legal basis: consent, contract, legitimate interest)
  3. How long you keep it (retention period for each data type)
  4. Who you share it with (third-party services, vendors, partners)
  5. User rights (access, correction, deletion, portability, objection)
  6. Contact for privacy questions (data protection officer, support email)

Example minimal privacy policy structure:

## Privacy Policy

### Data We Collect
- Registration data: Name, email, password hash
- Behavioral data: Pages visited, time spent, search queries
- Device data: IP address, browser type, device type
- Analytics: Google Analytics via Google Tag Manager

### Legal Basis
- Registration: Contract (provide service)
- Analytics: Consent (you must opt-in)
- Security: Legitimate interest (protect against fraud)

### Retention
- User account: Until deletion
- Analytics: 26 months (Google's default)
- Support tickets: 3 years (legal requirement)

### Your Rights
- Access: Request a copy of your data at [email protected]
- Correction: Edit profile information in account settings
- Deletion: Delete your account anytime (Right to be Forgotten)
- Portability: Request data export as JSON (email [email protected])

### Third Parties
- Google Analytics (analytics)
- Stripe (payments)
- SendGrid (email)

Each has its own privacy policy linked above.

Even if you use localStorage for consent preferences, you should set HTTP cookies with the same consent flags:

// Backend middleware
app.use((req, res, next) => {
const consentCookie = req.cookies.consentPreferences;

if (!consentCookie) {
// First visit, set a default (no analytics until user consents)
res.cookie("consentPreferences", JSON.stringify({ analytics: false, marketing: false }), {
maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year
httpOnly: false // JavaScript can read it to sync with localStorage
});
}

next();
});

// Only set Google Analytics cookie if user consented
app.use((req, res, next) => {
if (JSON.parse(req.cookies.consentPreferences).analytics) {
// Google Analytics tag is loaded (Consent Mode on)
}
next();
});

Data Minimization: Collect Only What You Need

GDPR requires data minimization: collect only the data you actually need. Audit your forms and analytics:

// Bad: Asking for unnecessary data
function RegistrationForm() {
return (
form>
<input name="firstName" placeholder="First Name" required />
<input name="lastName" placeholder="Last Name" required />
<input name="email" placeholder="Email" required />
<input name="phone" placeholder="Phone Number" /> {/* Unnecessary */}
<input name="address" placeholder="Address" /> {/* Unnecessary */}
<input name="dateOfBirth" placeholder="Date of Birth" /> {/* Unnecessary */}
<button type="submit">Register</button>
/form>
);
}

// Good: Ask for only what's needed
function RegistrationForm() {
return (
form>
<input name="email" placeholder="Email" required />
<input name="password" placeholder="Password" required />
<button type="submit">Register</button>
/form>
);
}

Key Takeaways

  • GDPR applies to any app collecting personal data from EU residents, regardless of where your server is.
  • Obtain explicit consent before collecting analytics, marketing, or non-essential cookies.
  • Store sensitive PII on the backend only (httpOnly cookies), not in localStorage.
  • Implement data deletion endpoints to honor "right to be forgotten" requests.
  • Publish a clear Privacy Policy explaining data collection, retention, and user rights.
  • Minimize data collection: ask for only what you need to provide your service.
  • Keep audit logs of data deletions for compliance verification.

Frequently Asked Questions

Does GDPR apply to my app if my users are only in the US?

No. GDPR applies to personal data of EU residents (including anyone in the EU, regardless of citizenship). If you have zero EU traffic, GDPR may not apply. However, CCPA (California) and LGPD (Brazil) have similar requirements. Assume you need privacy compliance if you have international users.

No. Under GDPR, Google Analytics (which sets cookies and sends data to Google) requires explicit opt-in consent. Some EU data protection authorities have ruled that Google Analytics with standard settings violates GDPR. Use Matomo or Fathom (privacy-friendly alternatives) or enable Consent Mode in Google Analytics (load scripts only after consent).

Consent means the user explicitly agrees. Legitimate interest means you can collect data without asking, as long as your interest outweighs the user's privacy. Security monitoring, fraud prevention, and service improvement can be justified as legitimate interest. But marketing, ads, and analytics require consent.

If I use a third-party service, am I liable for their data handling?

Yes. You are the "data controller" responsible for ensuring third-party processors comply with GDPR. Your contract with them must include data processing terms (Data Processing Agreement). If they mishandle data, GDPR fines apply to you.

How do I request a Data Processing Agreement from Google, Stripe, etc.?

Most SaaS platforms offer DPAs automatically (check their legal documents or customer portal). If not, contact their sales or legal team and request one. They will provide a standard DPA you sign, confirming they process data on your behalf.

Further Reading