React state array object changes without setState - reactjs

I have an array which is a state of the React component. This array is a checklist.
var units1 = this.state.units;
when I update units1, this.state.units changes without the this.setState({ units: units1 })
I use this.setState({ a: 2 }); just to see if the array was updated without this.setState({ units: units2 });
this.state.units gets its value from props so if the state changes the props also changes.
handleItemChange(e) {
var units1 = this.state.units.slice();
var unit_name = parseInt(e.target.attributes.getNamedItem('data-unit_name').value);
var new_unit;
if (!e.target.checked && this.state.units && this.state.units.length > 0) {
this.state.units.map((unit) => {
if (unit_name == unit.codpacunidad) {
if (unit.topics && unit.topics.length > 0) {
unit.topics.map((topic) => {
if (topic.codpacunidadtema == e.target.name) {
new_unit = unit;
var index = units1.indexOf(unit);
//units1.splice(index, 1);
units1 = update(units1, {$splice: [[index, 1]]})
var topics1 = unit.topics.slice();
index = topics1.indexOf(topic);
//topics1.splice(index, 1);
topics1 = update(topics1, {$splice: [[index, 1]]})
new_unit.topics = topics1;
}
});
}
}
});
} else {
var found_unit = false;
var name = parseInt(e.target.name);
var order = parseInt(e.target.attributes.getNamedItem('data-order').value);
var unit_order = parseInt(e.target.attributes.getNamedItem('data-unit_order').value);
if (this.state.units && this.state.units.length > 0) {
this.state.units.map((unit) => {
if (unit.codpacunidad == unit_name) {
found_unit = true;
new_unit = unit;
var index = units1.indexOf(unit);
units1.splice(index, 1);
var new_topic = {
codpacunidadtema: name,
orden: order
};
var topics2 = new_unit.topics;
new_unit.topics = update(topics2, { $push: [new_topic]});
}
});
}
if (found_unit == false) {
new_unit = {
codpacunidad: unit_name,
orden: unit_order,
topics: [{codpacunidadtema: name, orden: order }]
};
}
}
// var units2 = update(units1, { $push: [new_unit]});
// this.setState({ units: units2.sort(function(a, b) {
// return parseInt(a.orden) - parseInt(b.orden);
// })
// });
this.setState({ a: 2 }); //just to test if the array gets updated without this.setStaet({ units: units2 })
}
Anybody knows why this is happening?

As #Alexander van Oostenrijk said to make deep copy of array.
Because array are passed by reference which mean memory address of the array is passed not the value of array.
var units1 = this.state.units.slice();
Now units1 has the reference address of that array any change made to units1 or this.state.units.slice() will change value of both.As basically they are using address and if one change the value at address then both will read changed value.Hope you understand
To create deep copy you can create new object like
var units1 = Object.assign([],this.state.units)
This will create new object with data of this.state.units
extras I think you do not need .slice().

Related

trying to convetr json array to multiple json objects in nodejs

trying to convetr json array to multiple json objects in nodejs looking for help here is my code have tried loops to achieve that but failed just want to send the data as multiple objects not array
router.get('/frontpage-mobile', function (req, res, next) {
qryFilter = { "Product_Group": 'SIMPLE' };
// if we have a cart, pass it - otherwise, pass an empty object
var successMsg = req.flash('success')[0];
var errorMsg = req.flash('error')[0];
Product.find(qryFilter, function (err, product) {
// console.log("Product: " + JSON.stringify(product));
var pro=JSON.stringify(product)
if (err || product === 'undefined' || product == null) {
// replace with err handling
var errorMsg = req.flash('error', 'unable to find product');
res.send(errorMsg);
}
if (!product) {
req.flash('error', 'Product is not found.');
res.send('error', 'Product is not found.');
}
for(i=0;i<pro.length;i++)
res.send(pro[i]);
});
});
// Dummy Mongodb Result Array
var db_result = [{_id:1,name:"Cake_1"},{_id:2,name:"Cake_2"},{_id:3,name:"Cake_3"}]
// define data variable
var data = { total : 0, dataObj : {} } // default total = 0, = {}
var dataObj = {}
// convert array to obj
// { key1 : obj1, key2 : obj2, key3 : obj3, ... }
for(var i=0; i < db_result.length;i++){
let key = "key"+i;
dataObj[key] = db_result[i];
}
// assign data to variable
data.total = db_result.length;
data.dataObj = dataObj
// rend back to client
res.send(data);
// Result
{
"total":3,
"dataObj":{
"key0":{"_id":1,"name":"Cake_1"},
"key1":{"_id":2,"name":"Cake_2"},
"key2":{"_id":3,"name":"Cake_3"}
}
}

How to push a JSON object to an array in AngularJS

I need to push a JSON object to AngularJS and need to check before if the value for one of the objects exist. I need to overwrite the data.
$scope.setData = function(survey, choice) {
keepAllData.push({
'surveyId': survey.id,
'choiceId': choice.id
});
console.log(keepAllData);
toArray(keepAllData);
alert(JSON.stringify(toArray(keepAllData)));
$scope.keepAllDatas.push({
'surveyId': survey.id,
'choiceId': choice.id
});
var items = ($filter('filter')(keepAllDatas, {
surveyId: survey.id
}));
}
function toArray(obj) {
var result = [];
for (var prop in obj) {
var value = obj[prop];
console.log(prop);
if (typeof value === 'object') {
result.push(toArray(value));
console.log(result);
} else {
result.push(value);
console.log(result);
}
}
return result;
}
If the survey id exists in keepalldata, I need to change the recent value with choiceid. Is it possible to do with AngularJS?
Try with this: Before pushing data you have to check if the survey id exists or not. If it exists you have to update choice with the corresponding survey id, otherwise you can push directly.
$scope.setData = function(survey, choice) {
var item = $filter('filter')(keepAllData, {
surveyId: survey.id
});
if (!item.length) {
keepAllData.push({
'surveyId': survey.id,
'choiceId': choice.id
});
} else {
item[0].choiceId = choice.id;
}
console.log(keepAllData);
}
Demo
$scope.keepAllDatas = [];
$scope.setData = function(survey, choice) {
if($scope.keepAllDatas.length == 0) {
$scope.keepAllDatas.push({'surveyId':survey.id,'choiceId':choice.id});
}
else {
var items = ($filter('filter')( $scope.keepAllDatas, {surveyId: survey.id }));
for (var i = items.length - 1; i >= 0; i--) {
// alert(items[i].surveyId);
if(items[i].surveyId == survey.id) {
console.log($scope.keepAllDatas.indexOf(survey.id));
$scope.keepAllDatas.splice($scope.keepAllDatas.indexOf(survey.id),1);
console.log("Removed data")
}
}
$scope.keepAllDatas.push({'surveyId':survey.id, 'choiceId':choice.id});
console.log( $scope.keepAllDatas)
// alert(items[0].surveyId);
}
}

accessing items in firebase

I'm trying to learn firebase/angularjs by extending an app to use firebase as the backend.
My forge looks like this
.
In my program I have binded firebaseio.com/projects to $scope.projects.
How do I access the children?
Why doesn't $scope.projects.getIndex() return the keys to the children?
I know the items are in $scope.projects because I can see them if I do console.log($scope.projects)
app.js
angular.module('todo', ['ionic', 'firebase'])
/**
* The Projects factory handles saving and loading projects
* from localStorage, and also lets us save and load the
* last active project index.
*/
.factory('Projects', function() {
return {
all: function () {
var projectString = window.localStorage['projects'];
if(projectString) {
return angular.fromJson(projectString);
}
return [];
},
// just saves all the projects everytime
save: function(projects) {
window.localStorage['projects'] = angular.toJson(projects);
},
newProject: function(projectTitle) {
// Add a new project
return {
title: projectTitle,
tasks: []
};
},
getLastActiveIndex: function () {
return parseInt(window.localStorage['lastActiveProject']) || 0;
},
setLastActiveIndex: function (index) {
window.localStorage['lastActiveProject'] = index;
}
}
})
.controller('TodoCtrl', function($scope, $timeout, $ionicModal, Projects, $firebase) {
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
var keys = $scope.projects.$getIndex();
console.log($scope.projects.$child('-JGTmBu4aeToOSGmgCo1'));
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("" + keys[0]);
});
// A utility function for creating a new project
// with the given projectTitle
var createProject = function(projectTitle) {
var newProject = Projects.newProject(projectTitle);
$scope.projects.$add(newProject);
Projects.save($scope.projects);
$scope.selectProject(newProject, $scope.projects.length-1);
};
// Called to create a new project
$scope.newProject = function() {
var projectTitle = prompt('Project name');
if(projectTitle) {
createProject(projectTitle);
}
};
// Called to select the given project
$scope.selectProject = function(project, index) {
$scope.activeProject = project;
Projects.setLastActiveIndex(index);
$scope.sideMenuController.close();
};
// Create our modal
$ionicModal.fromTemplateUrl('new-task.html', function(modal) {
$scope.taskModal = modal;
}, {
scope: $scope
});
$scope.createTask = function(task) {
if(!$scope.activeProject || !task) {
return;
}
console.log($scope.activeProject.task);
$scope.activeProject.task.$add({
title: task.title
});
$scope.taskModal.hide();
// Inefficient, but save all the projects
Projects.save($scope.projects);
task.title = "";
};
$scope.newTask = function() {
$scope.taskModal.show();
};
$scope.closeNewTask = function() {
$scope.taskModal.hide();
};
$scope.toggleProjects = function() {
$scope.sideMenuController.toggleLeft();
};
// Try to create the first project, make sure to defer
// this by using $timeout so everything is initialized
// properly
$timeout(function() {
if($scope.projects.length == 0) {
while(true) {
var projectTitle = prompt('Your first project title:');
if(projectTitle) {
createProject(projectTitle);
break;
}
}
}
});
});
I'm interested in the objects at the bottom
console.log($scope.projects)
Update
After digging around it seems I may be accessing the data incorrectly. https://www.firebase.com/docs/reading-data.html
Here's my new approach
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
console.log(snapshot.val()['-JGTdgGAfq7dqBpSk2ls']);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
I'm still not sure how to traverse the keys programmatically but I feel I'm getting close
It's an object containing more objects, loop it with for in:
for (var key in $scope.projects) {
if ($scope.projects.hasOwnProperty(key)) {
console.log("The key is: " + key);
console.log("The value is: " + $scope.projects[key]);
}
}
ok so val() returns an object. In order to traverse all the children of projects I do
// Load or initialize projects
//$scope.projects = Projects.all();
var projectsUrl = "https://ionic-guide-harry.firebaseio.com/projects";
var projectRef = new Firebase(projectsUrl);
projectRef.on('value', function(snapshot) {
if(snapshot.val() === null) {
console.log('location does not exist');
} else {
var keys = Object.keys(snapshot.val());
console.log(snapshot.val()[keys[0]]);
}
});
$scope.projects = $firebase(projectRef);
$scope.projects.$on("loaded", function() {
// Grab the last active, or the first project
$scope.activeProject = $scope.projects.$child("a");
});
Note the var keys = Object.keys() gets all the keys at firebaseio.com/projects then you can get the first child by doing snapshot.val()[keys[0])

Using javascript prototype function to initialise variable in 'this' context

I'm finding it difficult to explain in words, so here's a snippet of code I'm trying out but Firefox/firebug goes into tailspin!
I'm trying to follow this and this as a guide. What I'm trying to do here is
new MyObject.Method('string',optionsArray);
optionsArray items are iterated and saved using the prototype function Set()
if(typeof(MyObj) == 'undefined') MyObj= {};
MyObj.Method = function initialise(id,options)
{
this.id = id;
this.options = options;
this.properties ={};
for (var i = 0; i < this.options.length; i++) // =>options.length=2 (correct)
{
var obj = this.options[i];
//get the keynames, pass with values to Set() to update properties
for (var keys in obj)
{
console.log(keys); //=> correctly prints 'property1' and 'currentValue'
this.Set(keys,obj); //=> this is i guess where it enters a loop?
}
}
}
//sets properties
MyObj.Method.prototype.Set = function (name, value)
{
this.properties[name.toLowerCase()] = value;
}
and in my html page script block, i have
window.onload = function () {
var options = [
{ property1: {
show: true,
min: 0,
max: 100
}
},
{
currentValue: {
show: true,
colour: 'black'
}
}
];
var myObj = new MyObj.Method('someDivId',options);
}
please advise if I'm over complicating the code. I think checking for hasOwnProperty would help.
This should be a cleaner way of achieving what you want:
function MyObj(id, options) { // a function that will get used as the constructor
this.id = id;
this.options = options;
this.properties = {};
this.set(options); // call the set method from the prototype
}
MyObj.prototype.set = function(options) { // set the options here
for(var i = 0, l = options.length; i < l; i++) {
var obj = this.options[i];
for(var key in obj) {
if (obj.hasOwnProperty(key)) { // this will exclude stuff that's on the prototype chain!
this.properties[key] = obj[key];
}
}
}
return this; // return the object for chaining purposes
// so one can do FooObj.set([...]).set([...]);
};
var test = new MyObj('simeDivId', [...]); // create a new instance of MyObj
test.set('bla', [...]); // set some additional options
Note: For what hasOwnProperty is about please see here.
I made a declaration for MyObj and removed the function name initialise since you're obviously declaring this function to be a property of MyObj. Your final code will then be like below, and that runs for me just fine. Please note that you cannot actually call the function until after you declare the prototype function because else the object will have no notion of the Set function.
var MyObj = {};
MyObj.Method = function (id,options)
{
this.id = id;
this.properties ={};
for (var i = 0; i < options.length; i++) // =>options.length=2 (correct)
{
var obj = options[i];
//get the keynames, pass with values to Set() to update properties
for (var keys in obj)
{
console.log(keys); //=> correctly prints 'property1' and 'currentValue'
this.Set(keys,obj); //=> this is i guess where it enters a loop?
}
}
}
MyObj.Method.prototype.Set = function (name, value)
{
this.properties[name.toLowerCase()] = value;
}
var options = [
{ property1: {
show: true,
min: 0,
max: 100
}
},
{
currentValue: {
show: true,
colour: 'black'
}
}
];
var myObj = new MyObj.Method('someDivId',options);
var MyObj = {};
MyObj.Method = function initialise(id,options) {
this.id = id;
this.options = options;
this.properties = {};
for (var i = 0; i < this.options.length; i++)
{
var obj = this.options[i];
for (var keys in obj) {
this.Set(keys,obj[keys]);
//*fix obj => obj[keys]
// (and it should be singular key rather then keys
}
}
console.log(this.properties) // will output what you want
}
//sets properties
MyObj.Method.prototype.Set = function (name, value) {
this.properties[name.toLowerCase()] = value;
}
var options = [{
property1: {
show: true,
min: 0,
max: 100
}
},{
currentValue: {
show: true,
colour: 'black'
}
}];
var myObj = new MyObj.Method('someDivId',options);
this should work problem is you had your myObj = new MyObj... outside your onload event and options was out of its scope as it was declared as private variable to the anonymous function bound to the onload event.
I've fixed also the way you was copying the values to the properties as it doubled the names of the property and made it a bit messy.

How to store objects and arrays in HTML5 localStorage? [duplicate]

I'd like to store a JavaScript object in HTML5 localStorage, but my object is apparently being converted to a string.
I can store and retrieve primitive JavaScript types and arrays using localStorage, but objects don't seem to work. Should they?
Here's my code:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
console.log('typeof testObject: ' + typeof testObject);
console.log('testObject properties:');
for (var prop in testObject) {
console.log(' ' + prop + ': ' + testObject[prop]);
}
// Put the object into storage
localStorage.setItem('testObject', testObject);
// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');
console.log('typeof retrievedObject: ' + typeof retrievedObject);
console.log('Value of retrievedObject: ' + retrievedObject);
The console output is
typeof testObject: object
testObject properties:
one: 1
two: 2
three: 3
typeof retrievedObject: string
Value of retrievedObject: [object Object]
It looks to me like the setItem method is converting the input to a string before storing it.
I see this behavior in Safari, Chrome, and Firefox, so I assume it's my misunderstanding of the HTML5 Web Storage specification, not a browser-specific bug or limitation.
I've tried to make sense of the structured clone algorithm described in 2 Common infrastructure. I don't fully understand what it's saying, but maybe my problem has to do with my object's properties not being enumerable (???).
Is there an easy workaround?
Update: The W3C eventually changed their minds about the structured-clone specification, and decided to change the spec to match the implementations. See 12111 – spec for Storage object getItem(key) method does not match implementation behavior. So this question is no longer 100% valid, but the answers still may be of interest.
Looking at the Apple, Mozilla and Mozilla again documentation, the functionality seems to be limited to handle only string key/value pairs.
A workaround can be to stringify your object before storing it, and later parse it when you retrieve it:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));
// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');
console.log('retrievedObject: ', JSON.parse(retrievedObject));
A minor improvement on a variant:
Storage.prototype.setObject = function(key, value) {
this.setItem(key, JSON.stringify(value));
}
Storage.prototype.getObject = function(key) {
var value = this.getItem(key);
return value && JSON.parse(value);
}
Because of short-circuit evaluation, getObject() will immediately return null if key is not in Storage. It also will not throw a SyntaxError exception if value is "" (the empty string; JSON.parse() cannot handle that).
You might find it useful to extend the Storage object with these handy methods:
Storage.prototype.setObject = function(key, value) {
this.setItem(key, JSON.stringify(value));
}
Storage.prototype.getObject = function(key) {
return JSON.parse(this.getItem(key));
}
This way you get the functionality that you really wanted even though underneath the API only supports strings.
Creating a facade for the Storage object is an awesome solution. That way, you can implement your own get and set methods. For my API, I have created a facade for localStorage and then check if it is an object or not while setting and getting.
var data = {
set: function(key, value) {
if (!key || !value) {return;}
if (typeof value === "object") {
value = JSON.stringify(value);
}
localStorage.setItem(key, value);
},
get: function(key) {
var value = localStorage.getItem(key);
if (!value) {return;}
// assume it is an object that has been stringified
if (value[0] === "{") {
value = JSON.parse(value);
}
return value;
}
}
Stringify doesn't solve all problems
It seems that the answers here don't cover all types that are possible in JavaScript, so here are some short examples on how to deal with them correctly:
// Objects and Arrays:
var obj = {key: "value"};
localStorage.object = JSON.stringify(obj); // Will ignore private members
obj = JSON.parse(localStorage.object);
// Boolean:
var bool = false;
localStorage.bool = bool;
bool = (localStorage.bool === "true");
// Numbers:
var num = 42;
localStorage.num = num;
num = +localStorage.num; // Short for "num = parseFloat(localStorage.num);"
// Dates:
var date = Date.now();
localStorage.date = date;
date = new Date(parseInt(localStorage.date));
// Regular expressions:
var regex = /^No\.[\d]*$/i; // Usage example: "No.42".match(regex);
localStorage.regex = regex;
var components = localStorage.regex.match("^/(.*)/([a-z]*)$");
regex = new RegExp(components[1], components[2]);
// Functions (not recommended):
function func() {}
localStorage.func = func;
eval(localStorage.func); // Recreates the function with the name "func"
I do not recommend to store functions, because eval() is evil and can lead to issues regarding security, optimisation and debugging.
In general, eval() should never be used in JavaScript code.
Private members
The problem with using JSON.stringify() for storing objects is, that this function can not serialise private members.
This issue can be solved by overwriting the .toString() method (which is called implicitly when storing data in web storage):
// Object with private and public members:
function MyClass(privateContent, publicContent) {
var privateMember = privateContent || "defaultPrivateValue";
this.publicMember = publicContent || "defaultPublicValue";
this.toString = function() {
return '{"private": "' + privateMember + '", "public": "' + this.publicMember + '"}';
};
}
MyClass.fromString = function(serialisedString) {
var properties = JSON.parse(serialisedString || "{}");
return new MyClass(properties.private, properties.public);
};
// Storing:
var obj = new MyClass("invisible", "visible");
localStorage.object = obj;
// Loading:
obj = MyClass.fromString(localStorage.object);
Circular references
Another problem stringify can't deal with are circular references:
var obj = {};
obj["circular"] = obj;
localStorage.object = JSON.stringify(obj); // Fails
In this example, JSON.stringify() will throw a TypeError "Converting circular structure to JSON".
If storing circular references should be supported, the second parameter of JSON.stringify() might be used:
var obj = {id: 1, sub: {}};
obj.sub["circular"] = obj;
localStorage.object = JSON.stringify(obj, function(key, value) {
if(key == 'circular') {
return "$ref" + value.id + "$";
} else {
return value;
}
});
However, finding an efficient solution for storing circular references highly depends on the tasks that need to be solved, and restoring such data is not trivial either.
There are already some question on Stack Overflow dealing with this problem: Stringify (convert to JSON) a JavaScript object with circular reference
There is a great library that wraps many solutions so it even supports older browsers called jStorage
You can set an object
$.jStorage.set(key, value)
And retrieve it easily
value = $.jStorage.get(key)
value = $.jStorage.get(key, "default value")
I arrived at this post after hitting on another post that has been closed as a duplicate of this - titled 'how to store an array in localstorage?'. Which is fine except neither thread actually provides a full answer as to how you can maintain an array in localStorage - however I have managed to craft a solution based on information contained in both threads.
So if anyone else is wanting to be able to push/pop/shift items within an array, and they want that array stored in localStorage or indeed sessionStorage, here you go:
Storage.prototype.getArray = function(arrayName) {
var thisArray = [];
var fetchArrayObject = this.getItem(arrayName);
if (typeof fetchArrayObject !== 'undefined') {
if (fetchArrayObject !== null) { thisArray = JSON.parse(fetchArrayObject); }
}
return thisArray;
}
Storage.prototype.pushArrayItem = function(arrayName,arrayItem) {
var existingArray = this.getArray(arrayName);
existingArray.push(arrayItem);
this.setItem(arrayName,JSON.stringify(existingArray));
}
Storage.prototype.popArrayItem = function(arrayName) {
var arrayItem = {};
var existingArray = this.getArray(arrayName);
if (existingArray.length > 0) {
arrayItem = existingArray.pop();
this.setItem(arrayName,JSON.stringify(existingArray));
}
return arrayItem;
}
Storage.prototype.shiftArrayItem = function(arrayName) {
var arrayItem = {};
var existingArray = this.getArray(arrayName);
if (existingArray.length > 0) {
arrayItem = existingArray.shift();
this.setItem(arrayName,JSON.stringify(existingArray));
}
return arrayItem;
}
Storage.prototype.unshiftArrayItem = function(arrayName,arrayItem) {
var existingArray = this.getArray(arrayName);
existingArray.unshift(arrayItem);
this.setItem(arrayName,JSON.stringify(existingArray));
}
Storage.prototype.deleteArray = function(arrayName) {
this.removeItem(arrayName);
}
example usage - storing simple strings in localStorage array:
localStorage.pushArrayItem('myArray','item one');
localStorage.pushArrayItem('myArray','item two');
example usage - storing objects in sessionStorage array:
var item1 = {}; item1.name = 'fred'; item1.age = 48;
sessionStorage.pushArrayItem('myArray',item1);
var item2 = {}; item2.name = 'dave'; item2.age = 22;
sessionStorage.pushArrayItem('myArray',item2);
common methods to manipulate arrays:
.pushArrayItem(arrayName,arrayItem); -> adds an element onto end of named array
.unshiftArrayItem(arrayName,arrayItem); -> adds an element onto front of named array
.popArrayItem(arrayName); -> removes & returns last array element
.shiftArrayItem(arrayName); -> removes & returns first array element
.getArray(arrayName); -> returns entire array
.deleteArray(arrayName); -> removes entire array from storage
In theory, it is possible to store objects with functions:
function store (a)
{
var c = {f: {}, d: {}};
for (var k in a)
{
if (a.hasOwnProperty(k) && typeof a[k] === 'function')
{
c.f[k] = encodeURIComponent(a[k]);
}
}
c.d = a;
var data = JSON.stringify(c);
window.localStorage.setItem('CODE', data);
}
function restore ()
{
var data = window.localStorage.getItem('CODE');
data = JSON.parse(data);
var b = data.d;
for (var k in data.f)
{
if (data.f.hasOwnProperty(k))
{
b[k] = eval("(" + decodeURIComponent(data.f[k]) + ")");
}
}
return b;
}
However, function serialization/deserialization is unreliable because it is implementation-dependent.
You cannot store a key value without a string format.
LocalStorage only supports string formats for keys/values.
That is why you should convert your data to string whatever it is an array or object.
To store data in localStorage, first of all stringify it using the JSON.stringify() method.
var myObj = [{name:"test", time:"Date 2017-02-03T08:38:04.449Z"}];
localStorage.setItem('item', JSON.stringify(myObj));
Then when you want to retrieve data, you need to parse the string to object again.
var getObj = JSON.parse(localStorage.getItem('item'));
It is recommended using an abstraction library for many of the features discussed here, as well as better compatibility. There are lots of options:
jStorage or simpleStorage ← my preference
localForage
alekseykulikov/storage
Lawnchair
Store.js ← another good option
OMG
localDataStorage
You can use localDataStorage to transparently store JavaScript data types (Array, BigInt, Boolean, Date, Float, Integer, String and Object). It also provides lightweight data obfuscation, automatically compresses strings, facilitates query by key (name) as well as query by (key) value, and helps to enforce segmented shared storage within the same domain by prefixing keys.
[DISCLAIMER] I am the author of the utility [/DISCLAIMER]
Examples:
localDataStorage.set( 'key1', 'Belgian' )
localDataStorage.set( 'key2', 1200.0047 )
localDataStorage.set( 'key3', true )
localDataStorage.set( 'key4', { 'RSK' : [1,'3',5,'7',9] } )
localDataStorage.set( 'key5', null )
localDataStorage.get( 'key1' ) // --> 'Belgian'
localDataStorage.get( 'key2' ) // --> 1200.0047
localDataStorage.get( 'key3' ) // --> true
localDataStorage.get( 'key4' ) // --> Object {RSK: Array(5)}
localDataStorage.get( 'key5' ) // --> null
As you can see, the primitive values are respected.
You can use ejson to store the objects as strings.
EJSON is an extension of JSON to support more types. It supports all JSON-safe types, as well as:
Date (JavaScript Date)
Binary (JavaScript Uint8Array or the result of EJSON.newBinary)
User-defined types (see EJSON.addType. For example, Mongo.ObjectID is implemented this way.)
All EJSON serializations are also valid JSON. For example an object with a date and a binary buffer would be serialized in EJSON as:
{
"d": {"$date": 1358205756553},
"b": {"$binary": "c3VyZS4="}
}
Here is my localStorage wrapper using ejson
https://github.com/UziTech/storage.js
I added some types to my wrapper including regular expressions and functions
Another option would be to use an existing plugin.
For example persisto is an open source project that provides an easy interface to localStorage/sessionStorage and automates persistence for form fields (input, radio buttons, and checkboxes).
(Disclaimer: I am the author.)
For TypeScript users willing to set and get typed properties:
/**
* Silly wrapper to be able to type the storage keys
*/
export class TypedStorage<T> {
public removeItem(key: keyof T): void {
localStorage.removeItem(key);
}
public getItem<K extends keyof T>(key: K): T[K] | null {
const data: string | null = localStorage.getItem(key);
return JSON.parse(data);
}
public setItem<K extends keyof T>(key: K, value: T[K]): void {
const data: string = JSON.stringify(value);
localStorage.setItem(key, data);
}
}
Example usage:
// write an interface for the storage
interface MyStore {
age: number,
name: string,
address: {city:string}
}
const storage: TypedStorage<MyStore> = new TypedStorage<MyStore>();
storage.setItem("wrong key", ""); // error unknown key
storage.setItem("age", "hello"); // error, age should be number
storage.setItem("address", {city:"Here"}); // ok
const address: {city:string} = storage.getItem("address");
https://github.com/adrianmay/rhaboo is a localStorage sugar layer that lets you write things like this:
var store = Rhaboo.persistent('Some name');
store.write('count', store.count ? store.count+1 : 1);
store.write('somethingfancy', {
one: ['man', 'went'],
2: 'mow',
went: [ 2, { mow: ['a', 'meadow' ] }, {} ]
});
store.somethingfancy.went[1].mow.write(1, 'lawn');
It doesn't use JSON.stringify/parse because that would be inaccurate and slow on big objects. Instead, each terminal value has its own localStorage entry.
You can probably guess that I might have something to do with rhaboo.
localStorage.setItem('obj',JSON.stringify({name:'Akash'})); // Set Object in localStorage
localStorage.getItem('obj'); // Get Object from localStorage
sessionStorage.setItem('obj',JSON.stringify({name:'Akash'})); // Set Object in sessionStorage
sessionStorage.getItem('obj'); // Get Object from sessionStorage
I made another minimalistic wrapper with only 20 lines of code to allow using it like it should:
localStorage.set('myKey',{a:[1,2,5], b: 'ok'});
localStorage.has('myKey'); // --> true
localStorage.get('myKey'); // --> {a:[1,2,5], b: 'ok'}
localStorage.keys(); // --> ['myKey']
localStorage.remove('myKey');
https://github.com/zevero/simpleWebstorage
I made a thing that doesn't break the existing Storage objects, but creates a wrapper so you can do what you want. The result is a normal object, no methods, with access like any object.
The thing I made.
If you want 1 localStorage property to be magic:
var prop = ObjectStorage(localStorage, 'prop');
If you need several:
var storage = ObjectStorage(localStorage, ['prop', 'more', 'props']);
Everything you do to prop, or the objects inside storage will be automatically saved into localStorage. You're always playing with a real object, so you can do stuff like this:
storage.data.list.push('more data');
storage.another.list.splice(1, 2, {another: 'object'});
And every new object inside a tracked object will be automatically tracked.
The very big downside: it depends on Object.observe() so it has very limited browser support. And it doesn't look like it'll be coming for Firefox or Edge anytime soon.
I found a way to make it work with objects that have cyclic references.
Let's make an object with cyclic references.
obj = {
L: {
L: { v: 'lorem' },
R: { v: 'ipsum' }
},
R: {
L: { v: 'dolor' },
R: {
L: { v: 'sit' },
R: { v: 'amet' }
}
}
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;
We can't do JSON.stringify here, because of the circular references.
LOCALSTORAGE.CYCLICJSON has .stringify and .parse just like normal JSON, but works with objects with circular references. ("Works" meaning parse(stringify(obj)) and obj are deep equal AND have identical sets of 'inner equalities')
But we can just use the shortcuts:
LOCALSTORAGE.setObject('latinUncles', obj)
recovered = LOCALSTORAGE.getObject('latinUncles')
Then, recovered will be "the same" to obj, in the following sense:
[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
]
Here is the implementation of LOCALSTORAGE
LOCALSTORAGE = (function(){
"use strict";
var ignore = [Boolean, Date, Number, RegExp, String];
function primitive(item){
if (typeof item === 'object'){
if (item === null) { return true; }
for (var i=0; i<ignore.length; i++){
if (item instanceof ignore[i]) { return true; }
}
return false;
} else {
return true;
}
}
function infant(value){
return Array.isArray(value) ? [] : {};
}
function decycleIntoForest(object, replacer) {
if (typeof replacer !== 'function'){
replacer = function(x){ return x; }
}
object = replacer(object);
if (primitive(object)) return object;
var objects = [object];
var forest = [infant(object)];
var bucket = new WeakMap(); // bucket = inverse of objects
bucket.set(object, 0);
function addToBucket(obj){
var result = objects.length;
objects.push(obj);
bucket.set(obj, result);
return result;
}
function isInBucket(obj){ return bucket.has(obj); }
function processNode(source, target){
Object.keys(source).forEach(function(key){
var value = replacer(source[key]);
if (primitive(value)){
target[key] = {value: value};
} else {
var ptr;
if (isInBucket(value)){
ptr = bucket.get(value);
} else {
ptr = addToBucket(value);
var newTree = infant(value);
forest.push(newTree);
processNode(value, newTree);
}
target[key] = {pointer: ptr};
}
});
}
processNode(object, forest[0]);
return forest;
};
function deForestIntoCycle(forest) {
var objects = [];
var objectRequested = [];
var todo = [];
function processTree(idx) {
if (idx in objects) return objects[idx];
if (objectRequested[idx]) return null;
objectRequested[idx] = true;
var tree = forest[idx];
var node = Array.isArray(tree) ? [] : {};
for (var key in tree) {
var o = tree[key];
if ('pointer' in o) {
var ptr = o.pointer;
var value = processTree(ptr);
if (value === null) {
todo.push({
node: node,
key: key,
idx: ptr
});
} else {
node[key] = value;
}
} else {
if ('value' in o) {
node[key] = o.value;
} else {
throw new Error('unexpected')
}
}
}
objects[idx] = node;
return node;
}
var result = processTree(0);
for (var i = 0; i < todo.length; i++) {
var item = todo[i];
item.node[item.key] = objects[item.idx];
}
return result;
};
var console = {
log: function(x){
var the = document.getElementById('the');
the.textContent = the.textContent + '\n' + x;
},
delimiter: function(){
var the = document.getElementById('the');
the.textContent = the.textContent +
'\n*******************************************';
}
}
function logCyclicObjectToConsole(root) {
var cycleFree = decycleIntoForest(root);
var shown = cycleFree.map(function(tree, idx) {
return false;
});
var indentIncrement = 4;
function showItem(nodeSlot, indent, label) {
var leadingSpaces = ' '.repeat(indent);
var leadingSpacesPlus = ' '.repeat(indent + indentIncrement);
if (shown[nodeSlot]) {
console.log(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
} else {
console.log(leadingSpaces + label + ' object#' + nodeSlot);
var tree = cycleFree[nodeSlot];
shown[nodeSlot] = true;
Object.keys(tree).forEach(function(key) {
var entry = tree[key];
if ('value' in entry) {
console.log(leadingSpacesPlus + key + ": " + entry.value);
} else {
if ('pointer' in entry) {
showItem(entry.pointer, indent + indentIncrement, key);
}
}
});
}
}
console.delimiter();
showItem(0, 0, 'root');
};
function stringify(obj){
return JSON.stringify(decycleIntoForest(obj));
}
function parse(str){
return deForestIntoCycle(JSON.parse(str));
}
var CYCLICJSON = {
decycleIntoForest: decycleIntoForest,
deForestIntoCycle : deForestIntoCycle,
logCyclicObjectToConsole: logCyclicObjectToConsole,
stringify : stringify,
parse : parse
}
function setObject(name, object){
var str = stringify(object);
localStorage.setItem(name, str);
}
function getObject(name){
var str = localStorage.getItem(name);
if (str===null) return null;
return parse(str);
}
return {
CYCLICJSON : CYCLICJSON,
setObject : setObject,
getObject : getObject
}
})();
obj = {
L: {
L: { v: 'lorem' },
R: { v: 'ipsum' }
},
R: {
L: { v: 'dolor' },
R: {
L: { v: 'sit' },
R: { v: 'amet' }
}
}
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;
// LOCALSTORAGE.setObject('latinUncles', obj)
// recovered = LOCALSTORAGE.getObject('latinUncles')
// localStorage not available inside fiddle ):
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(obj)
putIntoLS = LOCALSTORAGE.CYCLICJSON.stringify(obj);
recovered = LOCALSTORAGE.CYCLICJSON.parse(putIntoLS);
LOCALSTORAGE.CYCLICJSON.logCyclicObjectToConsole(recovered);
var the = document.getElementById('the');
the.textContent = the.textContent + '\n\n' +
JSON.stringify(
[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
]
)
<pre id='the'></pre>
This question has been answered sufficiently from the JavaScript-only perspective, and others have already noted that both localStorage.getItem and localStorage.setItem have no concept of objects—they handle strings and strings only. This answer provides a TypeScript-friendly solution that incorporates what others have suggested in JavaScript-only solutions.
TypeScript 4.2.3
Storage.prototype.setObject = function (key: string, value: unknown) {
this.setItem(key, JSON.stringify(value));
};
Storage.prototype.getObject = function (key: string) {
const value = this.getItem(key);
if (!value) {
return null;
}
return JSON.parse(value);
};
declare global {
interface Storage {
setObject: (key: string, value: unknown) => void;
getObject: (key: string) => unknown;
}
}
Usage
localStorage.setObject('ages', [23, 18, 33, 22, 58]);
localStorage.getObject('ages');
Explanation
We declare both setObject and getObject functions on the Storage prototype—localStorage is an instance of this type. There's nothing special we really need to note besides the null handling in getObject. Since getItem can return null, we must exit early since calling JSON.parse on a null value will throw a runtime exception.
After declaring the functions on the Storage prototype, we include their type definitions on the Storage type in the global namespace.
Note: If we defined these functions with arrow functions, we'd need to assume that the storage object we're calling is always localStorage, which might not be true. For instance, the above code will add setObject and getObject support to sessionStorage as well.
Here is some extended version of the code posted by danott:
It'll also implement a delete value from localstorage and shows how to adds a Getter and Setter layer so instead of,
localstorage.setItem(preview, true)
you can write
config.preview = true
Okay, here were go:
var PT=Storage.prototype
if (typeof PT._setItem >='u')
PT._setItem = PT.setItem;
PT.setItem = function(key, value)
{
if (typeof value >='u') //..undefined
this.removeItem(key)
else
this._setItem(key, JSON.stringify(value));
}
if (typeof PT._getItem >='u')
PT._getItem = PT.getItem;
PT.getItem = function(key)
{
var ItemData = this._getItem(key)
try
{
return JSON.parse(ItemData);
}
catch(e)
{
return ItemData;
}
}
// Aliases for localStorage.set/getItem
get = localStorage.getItem.bind(localStorage)
set = localStorage.setItem.bind(localStorage)
// Create ConfigWrapperObject
var config = {}
// Helper to create getter & setter
function configCreate(PropToAdd){
Object.defineProperty( config, PropToAdd, {
get: function () { return (get(PropToAdd) )},
set: function (val) { set(PropToAdd, val)}
})
}
//------------------------------
// Usage Part
// Create properties
configCreate('preview')
configCreate('notification')
//...
// Configuration Data transfer
// Set
config.preview = true
// Get
config.preview
// Delete
config.preview = undefined
Well, you may strip the aliases part with .bind(...). However, I just put it in since it's really good to know about this. I took me hours to find out why a simple get = localStorage.getItem; don't work.
I suggest using Jackson-js. It is a library that handles serializing and deserializing of Objects while retaining their structure, based on decorators.
The library handles all the pitfalls such as cyclic reference, attributes aliasing, etc.
Simply describe your class using the #JsonProperty() and #JsonClassType() decorators.
Serialize your object using:
const objectMapper = new ObjectMapper();
localstore.setItem(key, objectMapper.stringify<yourObjectType>(yourObject));
For slightly more detailed explanation, check my answer here:
Typescript objects serialization?
And the Jackson-js tutorial here:
Jackson-js: Powerful JavaScript decorators to serialize/deserialize objects into JSON and vice versa (Part 1)
Circular References
In this answer I focus on data-only objects (without functions, etc.) with circular references and develop ideas mentioned by maja and mathheadinclouds (I use his test case and
my code is several times shorter).
Actually, we can use JSON.stringify with a proper replacer - if the source object contains multi-references to some object, or contains circular references then we reference it by special path-string (similar to JSONPath).
// JSON.strigify replacer for objects with circ ref
function refReplacer() {
let m = new Map(), v = new Map(), init = null;
return function(field, value) {
let p = m.get(this) + (Array.isArray(this) ? `[${field}]` : '.' + field);
let isComplex = value === Object(value)
if (isComplex) m.set(value, p);
let pp = v.get(value)||'';
let path = p.replace(/undefined\.\.?/, '');
let val = pp ? `#REF:${pp[0] == '[' ? '$':'$.'}${pp}` : value;
!init ? (init=value) : (val===init ? val="#REF:$" : 0);
if(!pp && isComplex) v.set(value, path);
return val;
}
}
// ---------------
// TEST
// ---------------
// Generate obj with duplicate/circular references
let obj = {
L: {
L: { v: 'lorem' },
R: { v: 'ipsum' }
},
R: {
L: { v: 'dolor' },
R: {
L: { v: 'sit' },
R: { v: 'amet' }
}
}
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;
testObject = obj;
let json = JSON.stringify(testObject, refReplacer(), 4);
console.log("Test Object\n", testObject);
console.log("JSON with JSONpath references\n", json);
Parse such JSON content with JSONpath-like references:
// Parse JSON content with JSONpath references to object
function parseRefJSON(json) {
let objToPath = new Map();
let pathToObj = new Map();
let o = JSON.parse(json);
let traverse = (parent, field) => {
let obj = parent;
let path = '#REF:$';
if (field !== undefined) {
obj = parent[field];
path = objToPath.get(parent) + (Array.isArray(parent) ? `[${field}]` : `${field ? '.' + field : ''}`);
}
objToPath.set(obj, path);
pathToObj.set(path, obj);
let ref = pathToObj.get(obj);
if (ref) parent[field] = ref;
for (let f in obj) if (obj === Object(obj)) traverse(obj, f);
}
traverse(o);
return o;
}
// ---------------
// TEST 1
// ---------------
let json = `
{
"L": {
"L": {
"v": "lorem",
"uncle": {
"L": {
"v": "dolor",
"uncle": "#REF:$.L"
},
"R": {
"L": {
"v": "sit",
"uncle": "#REF:$.L.L.uncle.L"
},
"R": {
"v": "amet",
"uncle": "#REF:$.L.L.uncle.L"
},
"uncle": "#REF:$.L"
}
}
},
"R": {
"v": "ipsum",
"uncle": "#REF:$.L.L.uncle"
}
},
"R": "#REF:$.L.L.uncle"
}`;
let testObject = parseRefJSON(json);
console.log("Test Object\n", testObject);
// ---------------
// TEST 2
// ---------------
console.log('Tests from mathheadinclouds answer: ');
let recovered = testObject;
let obj = { // Original object
L: {
L: { v: 'lorem' },
R: { v: 'ipsum' }
},
R: {
L: { v: 'dolor' },
R: {
L: { v: 'sit' },
R: { v: 'amet' }
}
}
}
obj.R.L.uncle = obj.L;
obj.R.R.uncle = obj.L;
obj.R.R.L.uncle = obj.R.L;
obj.R.R.R.uncle = obj.R.L;
obj.L.L.uncle = obj.R;
obj.L.R.uncle = obj.R;
[
obj.L.L.v === recovered.L.L.v,
obj.L.R.v === recovered.L.R.v,
obj.R.L.v === recovered.R.L.v,
obj.R.R.L.v === recovered.R.R.L.v,
obj.R.R.R.v === recovered.R.R.R.v,
obj.R.L.uncle === obj.L,
obj.R.R.uncle === obj.L,
obj.R.R.L.uncle === obj.R.L,
obj.R.R.R.uncle === obj.R.L,
obj.L.L.uncle === obj.R,
obj.L.R.uncle === obj.R,
recovered.R.L.uncle === recovered.L,
recovered.R.R.uncle === recovered.L,
recovered.R.R.L.uncle === recovered.R.L,
recovered.R.R.R.uncle === recovered.R.L,
recovered.L.L.uncle === recovered.R,
recovered.L.R.uncle === recovered.R
].forEach(x => console.log('test pass: ' + x));
To load/save the resulting JSON content into storage, use the following code:
localStorage.myObject = JSON.stringify(testObject, refReplacer()); // Save
testObject = parseRefJSON(localStorage.myObject); // Load
localStorage.setItem('user', JSON.stringify(user));
Then to retrieve it from the store and convert to an object again:
var user = JSON.parse(localStorage.getItem('user'));
If we need to delete all entries of the store we can simply do:
localStorage.clear();

Resources