Advance JavaScript শিখুন বাংলায় (পর্ব ৯) — localStorage দিয়ে ডেটা Persist করা শিখুন

localStorage দিয়ে ডেটা persist করা

আগের পর্বে আমরা Inheritance দিয়ে ক্লাস হায়ারার্কি বানিয়েছিলাম। Advanced HTML সিরিজের পর্ব ১-এ আমরা localStorage-এর বেসিক শিখেছিলাম। এই পর্বে আমরা সেটা আরও গভীরে নিয়ে যাব — Class-ভিত্তিক অবজেক্ট কীভাবে সঠিকভাবে সেভ আর পুনরুদ্ধার করা যায়, আর কোন কোন সমস্যায় সতর্ক থাকতে হবে।

সমস্যাটা কী? — Class Instance হারিয়ে যায়

একটা সাধারণ প্লেইন অবজেক্ট JSON.stringify/JSON.parse দিয়ে ঠিকমতো কাজ করে, কিন্তু Class instance-এর ক্ষেত্রে একটা লুকানো সমস্যা আছে — মেথডগুলো হারিয়ে যায়!

class Book {
  constructor(title, author) {
    this.title = title;
    this.author = author;
  }
  getInfo() {
    return `${this.title} — ${this.author}`;
  }
}

const book = new Book("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর");

// সেভ করা
localStorage.setItem("book", JSON.stringify(book));

// ফেরত আনা
const loadedBook = JSON.parse(localStorage.getItem("book"));

console.log(loadedBook.title); // ✅ "সোনার তরী" — ঠিক আছে
console.log(loadedBook.getInfo()); // ❌ TypeError: loadedBook.getInfo is not a function!

JSON.stringify শুধু প্রপার্টি (data) সংরক্ষণ করে, ক্লাসের মেথড (যেগুলো prototype-এ থাকে) সংরক্ষণ করে না। JSON.parse থেকে ফেরত আসা অবজেক্টটা একটা প্লেইন অবজেক্ট, আর কোনো Book instance না।

সমাধান ১ — ফেরত পাওয়ার পর নতুন instance বানানো

সবচেয়ে সহজ সমাধান — plain object থেকে ম্যানুয়ালি আবার Class instance বানানো:

const rawData = JSON.parse(localStorage.getItem("book"));
const loadedBook = new Book(rawData.title, rawData.author); // আবার Class দিয়ে বানানো

console.log(loadedBook.getInfo()); // ✅ এখন কাজ করবে

সমাধান ২ — একটা fromJSON static মেথড

আগের পর্বে শেখা static মেথড ব্যবহার করে এই কাজটা ক্লাসের নিজের ভেতরেই সুন্দরভাবে গুছিয়ে রাখা যায় — একটা কনভেনশন হলো fromJSON নামে static মেথড রাখা।

class Book {
  constructor(title, author) {
    this.title = title;
    this.author = author;
  }

  getInfo() {
    return `${this.title} — ${this.author}`;
  }

  // plain object থেকে Book instance বানানো
  static fromJSON(data) {
    return new Book(data.title, data.author);
  }
}

// সেভ করা
localStorage.setItem("book", JSON.stringify(book));

// ফেরত আনা — এবার সঠিক Book instance
const rawData = JSON.parse(localStorage.getItem("book"));
const loadedBook = Book.fromJSON(rawData);

console.log(loadedBook.getInfo()); // ✅ কাজ করছে
console.log(loadedBook instanceof Book); // ✅ true

অ্যারে অফ ইনস্ট্যান্স সেভ করা

একাধিক Class instance একটা অ্যারেতে থাকলে, সেভ আর লোড করার সময় .map() দিয়ে প্রতিটাকে আলাদাভাবে রূপান্তর করতে হয়:

const books = [
  new Book("সোনার তরী", "রবীন্দ্রনাথ ঠাকুর"),
  new Book("পথের পাঁচালী", "বিভূতিভূষণ")
];

// সেভ করা — অ্যারে সরাসরি stringify করা যায়
localStorage.setItem("books", JSON.stringify(books));

// ফেরত আনা — প্রতিটাকে আলাদাভাবে fromJSON দিয়ে রূপান্তর
const rawBooks = JSON.parse(localStorage.getItem("books")) || [];
const loadedBooks = rawBooks.map((data) => Book.fromJSON(data));

loadedBooks.forEach((book) => console.log(book.getInfo()));
// এখন প্রতিটাই আসল Book instance, getInfo() কাজ করবে

লাইভ ডেমো — Class-ভিত্তিক নোট সংরক্ষণ

নিচে একটা Note ক্লাস ব্যবহার করে নোট যোগ করা, সেভ করা, আর পেজ রিলোড না করেই "রিলোড সিমুলেট" করে আবার লোড করা যায় — লক্ষ্য করো লোড করার পরও getPreview() মেথড ঠিকমতো কাজ করছে।

toJSON() দিয়ে সেভের ফরম্যাট নিয়ন্ত্রণ করা

যদি ক্লাসে এমন কিছু ডেটা থাকে যা সেভ করা উচিত না (যেমন প্রাইভেট ফিল্ড বা টেম্পোরারি স্টেট), ক্লাসে একটা toJSON() মেথড দিলে JSON.stringify স্বয়ংক্রিয়ভাবে সেটা ব্যবহার করে।

class Session {
  constructor(userId) {
    this.userId = userId;
    this.tempCache = new Map(); // এটা সেভ করার দরকার নেই, আর Map JSON-এ ভালোভাবে যায়ও না
  }

  // JSON.stringify কল হলে এই মেথডের ফলাফল ব্যবহার হবে
  toJSON() {
    return { userId: this.userId }; // শুধু userId সেভ হবে, tempCache বাদ
  }
}

const session = new Session("user123");
console.log(JSON.stringify(session)); // {"userId":"user123"} — tempCache নেই

Date অবজেক্ট নিয়ে সতর্কতা

Date অবজেক্ট JSON-এ রূপান্তরিত হলে একটা স্ট্রিং হয়ে যায়, আবার ফেরত আনলে সেটা Date অবজেক্ট থাকে না — এটাও ম্যানুয়ালি ঠিক করতে হয়:

const original = { createdAt: new Date() };
localStorage.setItem("data", JSON.stringify(original));

const loaded = JSON.parse(localStorage.getItem("data"));
console.log(typeof loaded.createdAt); // "string" — আর Date না!
console.log(loaded.createdAt.getFullYear); // ❌ undefined, ফাংশন নেই

// সমাধান — ফেরত আনার পর ম্যানুয়ালি Date-এ রূপান্তর
loaded.createdAt = new Date(loaded.createdAt);
console.log(loaded.createdAt.getFullYear()); // ✅ এখন কাজ করবে

একটা সম্পূর্ণ উদাহরণ — Library ক্লাস সম্পূর্ণ persist করা

চলো পর্ব ৭-এর Library ক্লাসটা এমনভাবে বানাই যাতে পুরোটাই localStorage-এ সেভ ও লোড করা যায়।

class Book {
  constructor(title, author) {
    this.title = title;
    this.author = author;
  }
  static fromJSON(data) {
    return new Book(data.title, data.author);
  }
}

class Library {
  #books = [];

  addBook(title, author) {
    this.#books.push(new Book(title, author));
    this.save();
  }

  get allBooks() {
    return [...this.#books];
  }

  save() {
    localStorage.setItem("myLibrary", JSON.stringify(this.#books));
  }

  load() {
    const raw = JSON.parse(localStorage.getItem("myLibrary")) || [];
    this.#books = raw.map((data) => Book.fromJSON(data));
  }
}

const library = new Library();
library.load(); // পেজ লোড হওয়ার সময় আগের ডেটা ফিরিয়ে আনা
library.addBook("নতুন বই", "নতুন লেখক"); // যোগ করলেই স্বয়ংক্রিয়ভাবে সেভ হয়ে যাবে

এই Library ক্লাসটা এখন সম্পূর্ণ স্বয়ংসম্পূর্ণ — সেভ, লোড, আর Class instance রক্ষণাবেক্ষণ সবকিছু ক্লাসের ভেতরেই সুন্দরভাবে গোছানো, যা Pathok Ghor-এর মতো প্রজেক্টে সরাসরি প্রয়োগ করা যায়।

নিজে চেষ্টা করো

Exercise:

  1. উপরের Library ক্লাসে একটা removeBook(index) মেথড যোগ করো, যেটা মোছার পর save()-ও স্বয়ংক্রিয়ভাবে কল করবে।
  2. পর্ব ৮-এর Content/Book/Article হায়ারার্কিতে fromJSON static মেথড যোগ করো, প্রতিটা ক্লাসের জন্য।
  3. একটা ক্লাস বানাও যাতে Date প্রপার্টি আছে, তারপর সেভ-লোড করে দেখো getFullYear()-এর মতো Date মেথড আগে ভাঙে কিনা, এবং সেটা ঠিক করো।
  4. উপরের লাইভ ডেমোর Note ক্লাসে একটা toJSON() মেথড যোগ করো যেটা শুধু প্রয়োজনীয় ডেটা সেভ করবে।

পরের পর্বে যা থাকছে

পরের পর্বে আমরা শিখব Error Handling বিস্তারিত — কাস্টম এরর ক্লাস বানানো (Inheritance-এর আরেকটা বাস্তব প্রয়োগ!), আর try/catch/finally-এর সঠিক ব্যবহার।

একটি মন্তব্য পোস্ট করুন

নবীনতর পূর্বতন

যোগাযোগ ফর্ম