LEARN TO CODE | JAVASCRIPT | 03 INTERACTIVE PAGES
03.4 Browser storage and JSON
Save a small non-sensitive preference, restore it safely after reload and distinguish browser storage from an account or database.
01 | WHAT SHOULD SURVIVE A RELOAD?
Your counter resets after a reload because its variable belongs to the current page execution. A preference such as “study JavaScript for 20 minutes” may be useful next time you open the page.
Browser storage can keep small pieces of data on the device. This lesson saves only a selected subject and duration, after the visitor explicitly chooses Save.
It is not a user account, cloud backup or shared database. Another browser profile or device will not automatically receive the same preference.
In VS Code, create index.html, script.js and, when shown, style.css in one practice folder. Save changes, open the HTML page in your browser and inspect both the page and its Console. Replace the previous example unless a task explicitly extends it.
For dependable storage tests, use a local HTTP preview server rather than opening a file URL. If your VS Code setup already has a teacher-approved preview extension, open the folder and use its preview command. With Live Server installed, right-click index.html and choose Open with Live Server. Keep the same address and port for later tests.
No Node installation is needed for the lesson programs. Direct file opening works for the earlier DOM/event examples, but file-URL storage behaviour varies. The storage example should be tested over HTTP.
02 | LOCALSTORAGE AND SESSIONSTORAGE
| Feature | localStorage | sessionStorage |
|---|---|---|
| Typical lifetime | Persists across ordinary browser sessions until cleared/evicted | Lives with the tab’s page session and survives reloads |
| Sharing | Same-origin tabs use the same storage area | Separate top-level tab sessions have separate areas |
| Stored values | Strings | Strings |
| Useful classroom example | A non-sensitive study preference | Temporary per-tab workflow state |
An origin is defined by the scheme, host and port. http://localhost:5500 and http://127.0.0.1:5500 are different origins. Changing the port also changes the origin, so a preference may appear missing when you opened a different address.
sessionStorage is browser-side storage, not the server-side sessions introduced in PHP. A tab opened from another may initially receive a copy in some circumstances; it is not a reliable shared cross-tab channel.
03 | KEYS IDENTIFY STORED STRINGS
try {
localStorage.setItem("csc-js03-example", "JavaScript");
const subject = localStorage.getItem("csc-js03-example");
console.log(subject);
localStorage.removeItem("csc-js03-example");
} catch (error) {
console.log("Storage is unavailable for this test.");
}
setItem stores a string under a key. getItem returns its string value or null if the key is absent. removeItem deletes that one entry.
Use a distinctive application-specific key. Do not call localStorage.clear() for a small feature: it removes all localStorage entries for that origin, potentially including other exercises or site features.
Access and writes can fail because of browser policy, privacy settings or quota limits. The try/catch includes the storage access itself so failure can be handled.
04 | JSON IS TEXT REPRESENTING DATA
const preference = { version: 1, subject: "JavaScript", minutes: 20 };
const text = JSON.stringify(preference);
console.log(typeof text);
const restored = JSON.parse(text);
console.log(restored.subject);
console.log(restored.minutes);
JSON.stringify serialises suitable data into JSON text. JSON.parse reads that text and constructs a JavaScript value. Store the text, then parse it when loading.
Without serialisation, storing an ordinary object directly commonly yields the string "[object Object]", not a recoverable record. JSON gives the fields a structured representation.
JSON is not JavaScript source: keys and strings use double quotes, and comments and trailing commas are not permitted. Never use eval to read saved JSON.
05 | JSON DOES NOT PRESERVE EVERYTHING
JSON suits plain records of strings, booleans, null, numbers and arrays. It does not preserve functions, undefined fields or every kind of JavaScript object. A Date normally serialises as text rather than automatically restoring as a Date.
Circular references can make stringification fail. Non-finite numeric values do not round-trip as ordinary numbers. Choose a simple data shape and validate it rather than treating JSON as a universal object backup.
Our record has a version marker, an allowed subject and a whole-minute duration. It has no methods and no sensitive fields.
06 | SUCCESSFUL PARSING IS NOT VALIDATION
The text 42 is valid JSON, but it is not our preference record. So is a record containing minutes:"twenty". Parsing proves the text is syntactically readable; it does not prove the data meets the app’s requirements.
validSettings checks that the result is a non-null object, not an array, has version 1, an allowed subject and an integer duration from 5 to 120. It ignores unrelated extra fields rather than using them.
Keep the null and object checks before accessing properties. The short-circuiting && expression stops as soon as a required check is false.
07 | A LOAD HAS SEVERAL POSSIBLE OUTCOMES
| Outcome | Page behaviour |
|---|---|
| No key | Show defaults; invite a deliberate save |
| Valid JSON and valid shape | Restore allowed settings |
| Valid JSON, wrong shape/version | Keep defaults; explain replacement on save |
| Malformed JSON or inaccessible storage | Keep defaults; show a controlled warning |
| Failed write | Say Not saved rather than claiming persistence |
The program does not delete an invalid entry automatically during loading. A later successful Save deliberately replaces it, or Forget removes this feature’s key. The visitor remains in control of those changes.
08 | COMPLETE PREFERENCE PROGRAM
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Remember a study preference</title>
<link rel="stylesheet" href="style.css">
<script src="script.js" defer></script>
</head>
<body>
<main>
<h1>Remember a study preference</h1>
<p>This demo saves only a subject and study duration in this browser when you select Save. It does not use an account or cloud sync.</p>
<form id="settings-form" novalidate>
<p><label for="settings-subject">Subject : </label> <select id="settings-subject" name="subject"><option value="JavaScript">JavaScript</option><option value="HTML">HTML</option><option value="SQL">SQL</option></select></p>
<p><label for="settings-minutes">Minutes, 5 to 120 : </label> <input id="settings-minutes" name="minutes" type="number" min="5" max="120" step="1" required value="20"></p>
<button type="submit">Save preference</button>
<button id="settings-forget" type="button">Forget this preference</button>
</form>
<p id="settings-status" role="status" aria-live="polite">Loading preference...</p>
</main>
</body>
</html>
script.js
const settingsKey = "csc-js03-study-preference-v1";
const allowedSubjects = ["JavaScript", "HTML", "SQL"];
const settingsForm = document.querySelector("#settings-form");
const subjectInput = document.querySelector("#settings-subject");
const minutesInput = document.querySelector("#settings-minutes");
const settingsStatus = document.querySelector("#settings-status");
function validSettings(value) {
return value !== null && typeof value === "object" && !Array.isArray(value)
&& value.version === 1
&& allowedSubjects.includes(value.subject)
&& Number.isInteger(value.minutes) && value.minutes >= 5 && value.minutes <= 120;
}
function showDefaults() {
subjectInput.value = "JavaScript";
minutesInput.value = "20";
}
function loadSettings() {
showDefaults();
try {
const text = localStorage.getItem(settingsKey);
if (text === null) {
settingsStatus.textContent = "No saved preference. Choose settings and select Save.";
return;
}
const value = JSON.parse(text);
if (!validSettings(value)) {
settingsStatus.textContent = "Saved data does not match this app. Defaults shown; save to replace it.";
return;
}
subjectInput.value = value.subject;
minutesInput.value = String(value.minutes);
settingsStatus.textContent = `Loaded: ${value.subject}, ${value.minutes} minutes.`;
} catch (error) {
settingsStatus.textContent = "Saved preference could not be loaded. Defaults shown; browser storage may be unavailable or data may be damaged.";
}
}
settingsForm.addEventListener("submit", (event) => {
event.preventDefault();
const text = minutesInput.value.trim();
const value = { version: 1, subject: subjectInput.value, minutes: Number(text) };
if (text === "" || !validSettings(value) || !settingsForm.reportValidity()) {
settingsStatus.textContent = "Choose an available subject and a whole duration from 5 to 120 minutes.";
return;
}
try {
localStorage.setItem(settingsKey, JSON.stringify(value));
settingsStatus.textContent = `Saved: ${value.subject}, ${value.minutes} minutes. Reload this page to test.`;
} catch (error) {
settingsStatus.textContent = "Not saved. Browser storage may be blocked or full.";
}
});
document.querySelector("#settings-forget").addEventListener("click", () => {
try {
localStorage.removeItem(settingsKey);
showDefaults();
settingsStatus.textContent = "This preference was forgotten. Defaults restored.";
} catch (error) {
settingsStatus.textContent = "The preference could not be removed. Check your browser settings.";
}
});
loadSettings();
09 | SAVE, RELOAD AND FORGET
This demo saves only a subject and study duration in this browser when you select Save. It does not use an account or cloud sync.
Loading preference...
Choose a subject and duration, select Save, then reload this page on the same origin. The controls should restore the saved values. Forget removes only this preference key and restores defaults.
If the page is opened as a file or the browser blocks storage, read the status message. A failed save must not be reported as successful. The downloaded example is easiest to test on a stable local HTTP address.
TRY IT YOURSELF
Save SQL and 35 minutes, reload, then forget and reload again. What should each stage show?
Reveal guidance
The first reload restores SQL and 35. Forget restores JavaScript and 20 and removes the saved key. The final reload shows the defaults with no saved preference.
10 | INSPECT AND TEST DAMAGED DATA
Chrome/Edge Developer Tools show storage under Application → Local Storage for the current origin; Firefox has a Storage panel. Find csc-js03-study-preference-v1. Do not edit unrelated site keys.
In your own practice app, replace its value with malformed JSON such as {, then reload. Next try valid JSON with the wrong structure, such as 42. Both should lead to defaults and a helpful message instead of an uncaught error.
In a separate test, keep an unrelated key and verify Forget leaves it untouched. This demonstrates why deleting one key is preferable to clearing the whole storage area.
11 | KNOW THE LIMITS
Storage is readable by scripts running with access to that origin and can be changed or deleted by the user. Do not store passwords, secret API keys or sensitive personal information here. It is not an access-control boundary.
Private browsing, browser cleanup and device changes can remove or isolate data. localStorage operations are synchronous and are best kept small. Larger or more demanding client databases may need IndexedDB; shared durable records need an appropriate backend.
Multiple same-origin tabs can overwrite one key. A storage event can notify other documents of changes, but the writing document does not receive that same notification. This starter app intentionally reloads to restore and does not implement live multi-tab synchronisation.
KEEP LEARNING
Previous page 03 Interactive pages: challenges
Download Section 03 example programs
Further reading: The DOM · Event listeners · Form validation · Browser storage