Search code examples
javascriptjsondata-structureslocal-storageinstance

Failing to call a constructor method on objects stored in the localStorage


So I'm working on an application that takes in data from users & store it as objects in an array, I then created functionality to store that data in the localStorage. Here is the basic demo of what I'm trying to do:

// constructor function

function Book(title, author, pages, read = false) {
  this.title = title;
  this.author = author;
  this.pages = pages;
  this.read = read;
  this.status = function() {
    console.log(this.read);
  }
}

// array to store objects

let mybooks = [];

// instances of the Book object

let book1 = new Book('Hello', 'John', 123, true);
let book2 = new Book('Hey', 'Jane', 13, false);
let book3 = new Book('Hi', 'Mary', 12, true);
let book4 = new Book('Holo', 'Peter', 10, false);
let book5 = new Book('Banda', 'Banda', 03, true);

// push the instances to the mybooks array

mybooks.push(book1);
mybooks.push(book2);
mybooks.push(book3);
mybooks.push(book4);
mybooks.push(book5);

// store mybooks to the localStorage

localStorage.mybooks = JSON.stringify(mybooks);

// retrieve data from the localStorage

let data = JSON.parse(localStorage.getItem('mybooks'));

// call the status() method on each object

data.forEach(book => {
  book.status();
});

By calling the .status() method on each object, I expect to get the boolean ( true/false ) value on the console. But I get this error:

Uncaught TypeError: book.status is not a function
    at app.js:36
    at Array.forEach (<anonymous>)
    at app.js:35

But if I run the same function on each object before storing it on the localStorage, I get the correct output.


Solution

  • Have a look at JSON.stringify ... any information about an object / instance which can not be transformed into a JSON conform key-value pair will be lost.

    Thus, a Book instance will be presented by a string as simple as e.g. this ...

    '{"title":"Hello","author":"John","pages":123,"read":true}'
    

    ... and in case of parsing such a string via JSON.parse one gets returned an object like ...

    { title: "Hello", author: "John", pages: 123, read: true }
    

    ... which still might be capable of representing a book through the data it carries, but it can not be used anymore like a real Book instance that e.g. features an inherited status method that at any time could be used for such an instance.

    In order to solve the problem the OP has to map the parsed array of raw book data items (the one from the local storage) into an array of real Book instances ...

    function Book(title, author, pages, read = false) {
      // just a container of book specific data.
      this.title = title;
      this.author = author;
      this.pages = pages;
      this.read = read;
    }
    Book.prototype.status = function() {
      // `status` preferably is implemented as prototype method.
      console.log(this.read);
    }
    
    
    const mybooks = [
      new Book('Hello', 'John', 123, true),
      new Book('Hey', 'Jane', 13, false),
      new Book('Hi', 'Mary', 12, true),
      new Book('Holo', 'Peter', 10, false),
      new Book('Banda', 'Banda', 03, true),
    ];
    // const [book1, book2, book3, book4, book5] = mybooks;
    
    mybooks.forEach(book => book.status());
    
    
    // localStorage.setItem('mybooks', JSON.stringify(mybooks));
    const storageValue = JSON.stringify(mybooks);
    
    console.log('storageValue :', storageValue);
    
    
    // const data = JSON.parse(localStorage.getItem('mybooks'));
    const storageData = JSON.parse(storageValue);
    
    console.log('storageData ... raw book data :', storageData);
    
    // there is no status method at neither raw book data item ...
    console.log('storageData[0].status :', storageData[0].status);
    console.log('storageData[4].status :', storageData[4].status);
    
    // ... but one nevertheless can work with each raw data item ...
    console.log('raw book data item `status` delegation call ...');
    
    storageData.forEach(rawBookDataItem =>
      // explicit delegation via `call`.
      Book.prototype.status.call(rawBookDataItem)
    );
    
    
    // map/transform raw book data items each into a `Book` instance ...
    const listOfBookInstances = storageData.map(data =>
      new Book(data.title, data.author, data.pages, data.read)
    );
    console.log('listOfBookInstances :', listOfBookInstances);
    
    // call `status` method on each (new) book instance.
    listOfBookInstances.forEach(book => book.status());
    .as-console-wrapper { min-height: 100%!important; top: 0; }