How to update object in Mobx observable array - javascript

Codesandbox to the problem:
https://codesandbox.io/s/serverless-thunder-6gj04?file=/src/store.js
I have the following store:
class PersonStore {
persons = [];
constructor() {
makeAutoObservable(this, {}, { autoBind: true });
}
*fetchPersons() {
const response = yield fetch(
"https://random-data-api.com/api/users/random_user?size=5"
);
this.persons = yield response.json();
}
}
export default new PersonStore();
Now, I want to update persons inside the person-list.
When I update a single field on on items inside the array like this it works as expected and the UI updates:
update(id) {
let updated = this.persons.find((p) => p.id === id);
// This works...
updated.first_name = "FOO";
}
However, in the future I would like to pass more complex, updated data into this function. So my idea was to basically assign a totally new object with the updated values in the list.
Unfortunately, this does not work as I expected it:
update(id) {
let updated = this.persons.find((p) => p.id === id);
// This does not work...
const dummy_person = { first_name: 'foo', last_name: 'bar', id: 99 }
updated = dummy_person
}
My first guess was that this does not work because the objects inside the array are not "normal objects" but observables. So I created a model for the persons:
class Person {
id = null;
first_name = "";
last_name = "";
constructor() {
makeAutoObservable(this);
this.first_name = "FOO";
this.last_name = "BAR";
}
}
...but this does still not work...
update(id) {
let updated = this.persons.find((p) => p.id === id);
// This does not work...
const dummy_person = new Person()
updated = person
}
How can I "replace" an object inside the array here with an object containing the updated data?

As #Tholle said in the comment, you are only updating local variable, not actual object.
In simpler words this is what's happening inside your update function:
let updated = this.persons.find((p) => p.id === id); - you create local variable updated and assign some object (person) to it
const dummy_person = { first_name: 'foo', last_name: 'bar', id: 99 } - you create dummy object and assign it to local dummy_person constant
updated = dummy_person - here you assign dummy_person value to updated variable. Basically you only reassign value of updated variable, you are not changing the person-object, but only changing to what value updated variable points.
Sometimes people explain it with boxes, like imagine that updated is a box and at first you put person inside of it and then changed you mind and put dummy_person inside, but actual person wasn't changed.
So what can you do?
As #Tholle said, you can use splice to change persons array, it will basically throw away old person and insert new one.
Or if you actually want to update old person you could use Object.assign(updated, dummy_person)
Or you could use map (although it will reassign whole array, might be unnecessary sometimes):
update(id) {
const dummy_person = { first_name: 'foo', last_name: 'bar', id: 99 };
this.persons = this.persons.map(person => person.id === id ? dummy_person : person);
}
There is a lot what you can do, depends on what you what. The main thing is understand how reactivity works!
More about this topic in the docs: https://mobx.js.org/understanding-reactivity.html

Related

How to append properties in an object in Vuejs

I have Data in vuejs like following:
params: {
comment: Object,
state: "",
deleteAppointment: false,
appointmentId: null,
}
I am filling data from two functions. First function is just assigning following lines:
this.params.state = "BLACKLIST";
this.params.deleteAppointment = true;
this.params.appointmentId = this.appointmentId;
But, in the second function when I am assigning following:
const comment = {};
fd.forEach(function(value, key){
comment[key] = value;
});
const data = {};
Object.keys(this.lead).map((key) => {
if (this.lead[key] != this.orginal[key]) {
data[key] = this.lead[key];
}
});
this.params = data; // May be the problem is here, data is overwriting existing properties of params
this.params.comment = comment;
When assigning data in params, previous properties are vanishing!
May be I need object copy or something! I couldn't understand what I have to do actually right now.
You should inherit the previous object using Spread Operator, try this
this.params = {...this.params, ...data};

Is there a way to force a javascript element to redefine itself based on how it was originally defined?

I was playing around with objects and constructors and stuff like that, and I was wondering if there was a way to bind a value to a variable based on how it was originally defined. I have the following code:
typescript
let cr = "create",
ap = "apply",
$this = {
set: (prop, value) => {
this[prop] = value;
}
};
function creator() {
this.$ = (array: Object[]) => {
array.forEach((kp: Object) => {
let key = Object.keys(kp)[0];
let val = kp[Object.keys(kp)];
$this[key] = val;
creator.create(key, { value: val });
});
};
this.apply = (...objects: Object[]) => {
objects.forEach((obj: Object) => {
creator.call(obj);
});
};
}
function create(obj) {
function createValues(arr) {
let instance: Object = new obj();
let vals: any[] = [];
arr.forEach(name => {
vals.push(instance[name]);
});
return vals;
}
let names: string[] = Object.getOwnPropertyNames(new obj());
let values: string[] = createValues(names);
return combineArrays(names, values);
}
function combineArrays(arr1, arr2): { $?: any } { // the question mark removes an IDE error
let newObj: Object = {};
arr1.forEach(prop => {
newObj[prop] = arr2[arr1.indexOf(prop)];
});
return newObj;
}
Object.prototype.create = function(prop, options) {
return Object.defineProperty(this, prop, options);
};
create(creator).$([
{ hi: "hi" },
{ bye: $this["hi"] } // this is the important stuff
]);
I was wondering if there is a way, inside the set function of the $this variable, to detect how it is being set and therefore determine if that value has changed and so it's value should to, if that makes any sense? Let's say you had this:
let $this = {
set: function(prop, value) {
this[prop] = value;
}
}
let name = 'Ezra';
$this['name'] = name;
// then :
name = 'Bob';
// change $this.name too, so then:
console.log($this.name);
// >> 'Bob'
I believe this is called Data-Binding but I am unsure how to do it without creating endless numbers of proxies.
What you're describing is not really "data-binding" but pass-by-reference. In your example you expect an update to name to be reflected in $this['name']. That would only be possible if you were passing a reference (or a pointer) to the variable.
However, in this case the variable is a string, and strings in JavaScript are immutable:
no string methods change the string they operate on, they all return new strings. The reason is that strings are immutable – they cannot change, we can only ever make new strings.
So, going step-by-step through your example:
This creates a new string named 'Ezra', and assigns a variable called name to reference that string.
let name = 'Ezra';
This creates (or sets) a property in $this called 'name' that references the string in name (which is 'Ezra').
$this['name'] = name;
This creates a new string called 'Bob' and stores it in the variable called name. The variable already exists. This does not mutate the value that was previously held in there. Instead, name is being updated to point to a new reference.
// then :
name = 'Bob';
However, if you were to pass an object, you'll notice that you can actually mutate it. This is because objects are passed-by-reference and you can mutate the value at that reference.
For example:
// Create a new value that stores an object, with a property 'firstName'
let name = { firstName: 'Ezra' };
// Assign myObject to $this['name']. Now $this['name'] and name both point to the same reference.
$this['name'] = name;
// Change the value in the object pointed-to by name
name.firstName = 'Bob'
console.log($this['name'].firstName); // <- This will output 'Bob'

Does this method give completely private properties in Javascript?

EDIT: IT DOES NOT WORK
Thanks #loganfsmyth
Based on this Private properties in JavaScript ES6 classes, using Symbol() seems to be the best way because:
Methods are on prototype
Clean syntax
Almost private
But only "almost" since we can use Object.getOwnPropertySymbols() to loop through the properties. Now I have fixed the method a little bit, using closure.
const Person = (function () {
const myKey = {};
const lock = Symbol();
return class {
constructor(name, age) {
const data = { name, age }; // PRIVATE PROPERTIES GO INSIDE DATA
this[lock] = key => key === myKey ? data : null; // Lock check
}
info() {
const data = this[lock](myKey); // Open lock for every method
return `Name: ${data.name}, age: ${data.age}`;
}
}
})()
// Extending
const Student = (function () {
const myKey = {};
const lock = Symbol();
return class extends Person {
constructor(name, age, school) {
super(name, age);
const data = { school }; // PRIVATE PROPERTIES GO INSIDE DATA
this[lock] = key => key === myKey ? data : null; // Lock check
}
info() {
const data = this[lock](myKey); // Open lock for every method
return `${super.info()}, school: ${data.school}`;
}
}
})()
var ryan = new Student('Ryan Vu', 25, 'Seneca College');
var jane = new Student('Jane Vu', 29, 'Queen University');
console.log(ryan.info());
console.log(jane.info());
//Name: Ryan Vu, age: 25, school: Seneca College
//Name: Jane Vu, age: 29, school: Queen University
It does have some bad points such as you have to call the lock function for every methods, but overall if your goal is to have a completely private class, I think this is a good way, and it follows the rule of prototype chain also. I'm posting here because I'm not sure if what I think is correct, I'm not experienced in programming. Please correct me. Thank you.
Edit - Brief explanation: All methods get access to private data through a lock. Although the outside can see the lock, only the methods and the lock itself can see the key.
This approach does not work because you can still update the lock function with your own version, and use that version to get at the key, e.g.
var person = new Person();
var lock = Object.getOwnPropertySymbols(person)[0];
var key = (function(){
let key = null;
let origLock = person[lock];
person[lock] = function(k){ key = k; return origLock.apply(this, arguments); };
person.info();
return key;
})();
const data = person[lock](key);
What I think would work would be to make the property non-configurable, thus changing
this[lock] = key => key === myKey ? data : null;
to
Object.defineProperty(this, lock, {
value: key => key === myKey ? data : null,
configurable: false, // this is the default, here for clarity, but omittable.
});
This prevents anything from re-assigning the [lock] property.
That said, by the time you get this far, you've essentially implemented a WeakMap polyfill. You could just as well consider doing
const Person = (function () {
// Pull these methods off the prototype so that malicious code can't
// reassign them to get data about the private accesses.
const { set, get } = WeakMap.prototype;
const storage = new WeakMap();
return class {
constructor(name, age) {
set.call(storage, this, { name, age }); // PRIVATE PROPERTIES GO INSIDE DATA
}
info() {
const data = get.call(storage, this); // Open lock for every method
return `Name: ${data.name}, age: ${data.age}`;
}
}
})();
var person = new Person();
console.log(person);

Separating truthy and falsey values from an object

I'm working on a java challenge that reads
//In the function below you'll be passed a user object. Loop through the user object checking to make sure that each value is truthy. If it's not truthy, remove it from the object. Then return the object. hint: 'delete'.
function truthyObjLoop(user) {
//code here
I came up with....
var user = {};
user.name = {};
user.age = {};
if (user !== false)
{
return user;
} else {
delete user;
}
}
However whenever I try it it comes back with the error Function returned
{"name":{},"age":{}}
instead of
{"name":"ernest","age":50}
when passed
{"name":"ernest","age":50,"funky":false}....
Can anyone help me understand why this is happening or if I'm using the wrong symbols here? Thank you.
var user = {};
user.name = {};
user.age = {};
The user name and age should be the values, not Objects. curly braces mentions objects, you are wrong.try my following code please
var user = {};
user.name = "john";
user.age = 12;
Also read this tutorial please :
http://www.w3schools.com/json/
The text says "you'll be passed a user object". That means the user is being defined outside of the function, and passed in. Here's what you're looking for:
function truthyObjLoop(user) {
for (var k in user) {
if (!user[k]) delete user[k]
}
console.log(user);
}
You then should pass in a "user" with all sorts of different attributes to demonstrate how the test works:
var person = {
age: 29,
gender: "male",
pets: [
{
type: "cat",
name: "smelly"
}
],
children: 0,
married: false
};
truthyObjLoop(person);
Here's a jsfiddle:
https://jsfiddle.net/mckinleymedia/tb823pyp/
Notice it removes the attributes with a value of 'false' or '0'.

How to restore original object/type from JSON?

Its easy to load JSON into an object in javascript using eval or JSON.parse.
But if you have a proper "class" like function, how do you get the JSON data into it?
E.g.
function Person(name) {
this.name=name;
this.address = new Array();
this.friendList;
this.promote = function(){
// do some complex stuff
}
this.addAddress = function(address) {
this.address.push(address)
}
}
var aPersonJSON = '{\"name\":\"Bob\",\"address\":[{\"street\":\"good st\",\"postcode\":\"ADSF\"}]}'
var aPerson = eval( "(" + aPersonJSON + ")" ); // or JSON.parse
//alert (aPerson.name); // Bob
var someAddress = {street:"bad st",postcode:"HELL"};
//alert (someAddress.street); // bad st
aPerson.addAddress(someAddress); // fail!
The crux is I need to be able to create proper Person instances from JSON, but all I can get is a dumb object. Im wondering if its possible to do something with prototypes?
I dont want to have to parse each line of the JSON and assign each variable to the coresponding functions attributes, which would be too difficult. The actualy JSON and functions I have are much more complicated than the example above.
I am assuming one could JSONify the functions methods into the JSON string, but as I need to keep the resultant data as small as possible this is not an option - I only want to store and load the data, not the javascript code for the methods.
I also dont want to have to put the data loaded by JSON as a sub object if I can help it (but might be the only way), e.g.
function Person(name) {
this.data = {};
this.data.name=name;
}
var newPerson = new Person("");
newPerson.data = eval( "(" + aPersonJSON + ")" );
alert (newPerson.data.name); // Bob
Any ideas?
You need to use a reviver function:
// Registry of types
var Types = {};
function MyClass(foo, bar) {
this._foo = foo;
this._bar = bar;
}
Types.MyClass = MyClass;
MyClass.prototype.getFoo = function() {
return this._foo;
}
// Method which will provide a JSON.stringifiable object
MyClass.prototype.toJSON = function() {
return {
__type: 'MyClass',
foo: this._foo,
bar: this._bar
};
};
// Method that can deserialize JSON into an instance
MyClass.revive = function(data) {
// TODO: do basic validation
return new MyClass(data.foo, data.bar);
};
var instance = new MyClass('blah', 'blah');
// JSON obtained by stringifying an instance
var json = JSON.stringify(instance); // "{"__type":"MyClass","foo":"blah","bar":"blah"}";
var obj = JSON.parse(json, function(key, value) {
return key === '' && value.hasOwnProperty('__type')
? Types[value.__type].revive(value)
: this[key];
});
obj.getFoo(); // blah
No other way really...
Many frameworks provide an 'extend' function that will copy fields over from one object to another. You can combine this with JSON.parse to do what you want.
newPerson = new Person();
_.extend(newPerson, JSON.parse(aPersonJSON));
If you don't want to include something like underscore you can always copy over just the extend function or write your own.
Coffeescript example because I was bored:
JSONExtend = (obj, json) ->
obj[field] = value for own field, value of JSON.parse json
return obj
class Person
toString: -> "Hi I'm #{#name} and I'm #{#age} years old."
dude = JSONExtend new Person, '{"name":"bob", "age":27}'
console.log dude.toString()
A little late to the party, but this might help someone.
This is how I've solved it, ES6 syntax:
class Page
{
constructor() {
this.__className = "Page";
}
__initialize() {
// Do whatever initialization you need here.
// We'll use this as a makeshift constructor.
// This method is NOT required, though
}
}
class PageSection
{
constructor() {
this.__className = "PageSection";
}
}
class ObjectRebuilder
{
// We need this so we can instantiate objects from class name strings
static classList() {
return {
Page: Page,
PageSection: PageSection
}
}
// Checks if passed variable is object.
// Returns true for arrays as well, as intended
static isObject(varOrObj) {
return varOrObj !== null && typeof varOrObj === 'object';
}
static restoreObject(obj) {
let newObj = obj;
// At this point we have regular javascript object
// which we got from JSON.parse. First, check if it
// has "__className" property which we defined in the
// constructor of each class
if (obj.hasOwnProperty("__className")) {
let list = ObjectRebuilder.classList();
// Instantiate object of the correct class
newObj = new (list[obj["__className"]]);
// Copy all of current object's properties
// to the newly instantiated object
newObj = Object.assign(newObj, obj);
// Run the makeshift constructor, if the
// new object has one
if (newObj.__initialize === 'function') {
newObj.__initialize();
}
}
// Iterate over all of the properties of the new
// object, and if some of them are objects (or arrays!)
// constructed by JSON.parse, run them through ObjectRebuilder
for (let prop of Object.keys(newObj)) {
if (ObjectRebuilder.isObject(newObj[prop])) {
newObj[prop] = ObjectRebuilder.restoreObject(newObj[prop]);
}
}
return newObj;
}
}
let page = new Page();
let section1 = new PageSection();
let section2 = new PageSection();
page.pageSections = [section1, section2];
let jsonString = JSON.stringify(page);
let restoredPageWithPageSections = ObjectRebuilder.restoreObject(JSON.parse(jsonString));
console.log(restoredPageWithPageSections);
Your page should be restored as an object of class Page, with array containing 2 objects of class PageSection. Recursion works all the way to the last object regardless of depth.
#Sean Kinsey's answer helped me get to my solution.
Easiest way is to use JSON.parse to parse your string then pass the object to the function. JSON.parse is part of the json2 library online.
I;m not too much into this, but aPerson.addAddress should not work,
why not assigning into object directly ?
aPerson.address.push(someAddress);
alert(aPerson.address); // alert [object object]
Just in case someone needs it, here is a pure javascript extend function
(this would obviously belong into an object definition).
this.extend = function (jsonString){
var obj = JSON.parse(jsonString)
for (var key in obj) {
this[key] = obj[key]
console.log("Set ", key ," to ", obj[key])
}
}
Please don't forget to remove the console.log :P
TL; DR: This is approach I use:
var myObj = JSON.parse(raw_obj_vals);
myObj = Object.assign(new MyClass(), myObj);
Detailed example:
const data_in = '{ "d1":{"val":3,"val2":34}, "d2":{"val":-1,"val2":42, "new_val":"wut?" } }';
class Src {
val1 = 1;
constructor(val) { this.val = val; this.val2 = 2; };
val_is_good() { return this.val <= this.val2; }
get pos_val() { return this.val > 0; };
clear(confirm) { if (!confirm) { return; }; this.val = 0; this.val1 = 0; this.val2 = 0; };
};
const src1 = new Src(2); // standard way of creating new objects
var srcs = JSON.parse(data_in);
// ===================================================================
// restoring class-specific stuff for each instance of given raw data
Object.keys(srcs).forEach((k) => { srcs[k] = Object.assign(new Src(), srcs[k]); });
// ===================================================================
console.log('src1:', src1);
console.log("src1.val_is_good:", src1.val_is_good());
console.log("src1.pos_val:", src1.pos_val);
console.log('srcs:', srcs)
console.log("srcs.d1:", srcs.d1);
console.log("srcs.d1.val_is_good:", srcs.d1.val_is_good());
console.log("srcs.d2.pos_val:", srcs.d2.pos_val);
srcs.d1.clear();
srcs.d2.clear(true);
srcs.d3 = src1;
const data_out = JSON.stringify(srcs, null, '\t'); // only pure data, nothing extra.
console.log("data_out:", data_out);
Simple & Efficient. Compliant (in 2021). No dependencies.
Works with incomplete input, leaving defaults instead of missing fields (particularly useful after upgrade).
Works with excessive input, keeping unused data (no data loss when saving).
Could be easily extended to much more complicated cases with multiple nested classes with class type extraction, etc.
doesn't matter how much data must be assigned or how deeply it is nested (as long as you restore from simple objects, see Object.assign() limitations)
The modern approach (in December 2021) is to use #badcafe/jsonizer : https://badcafe.github.io/jsonizer
Unlike other solutions, it doesn't pollute you data with injected class names,
and it reifies the expected data hierarchy.
below are some examples in Typescript, but it works as well in JS
Before showing an example with a class, let's start with a simple data structure :
const person = {
name: 'Bob',
birthDate: new Date('1998-10-21'),
hobbies: [
{ hobby: 'programming',
startDate: new Date('2021-01-01'),
},
{ hobby: 'cooking',
startDate: new Date('2020-12-31'),
},
]
}
const personJson = JSON.stringify(person);
// store or send the data
Now, let's use Jsonizer 😍
// in Jsonizer, a reviver is made of field mappers :
const personReviver = Jsonizer.reviver<typeof person>({
birthDate: Date,
hobbies: {
'*': {
startDate: Date
}
}
});
const personFromJson = JSON.parse(personJson, personReviver);
Every dates string in the JSON text have been mapped to Date objects in the parsed result.
Jsonizer can indifferently revive JSON data structures (arrays, objects) or class instances with recursively nested custom classes, third-party classes, built-in classes, or sub JSON structures (arrays, objects).
Now, let's use a class instead :
// in Jsonizer, a class reviver is made of field mappers + an instance builder :
#Reviver<Person>({ // 👈 bind the reviver to the class
'.': ({name, birthDate, hobbies}) => new Person(name, birthDate, hobbies), // 👈 instance builder
birthDate: Date,
hobbies: {
'*': {
startDate: Date
}
}
})
class Person {
constructor( // all fields are passed as arguments to the constructor
public name: string,
public birthDate: Date
public hobbies: Hobby[]
) {}
}
interface Hobby {
hobby: string,
startDate: Date
}
const person = new Person(
'Bob',
new Date('1998-10-21'),
[
{ hobby: 'programming',
startDate: new Date('2021-01-01'),
},
{ hobby: 'cooking',
startDate: new Date('2020-12-31'),
},
]
);
const personJson = JSON.stringify(person);
const personReviver = Reviver.get(Person); // 👈 extract the reviver from the class
const personFromJson = JSON.parse(personJson, personReviver);
Finally, let's use 2 classes :
#Reviver<Hobby>({
'.': ({hobby, startDate}) => new Hobby(hobby, startDate), // 👈 instance builder
startDate: Date
})
class Hobby {
constructor (
public hobby: string,
public startDate: Date
) {}
}
#Reviver<Person>({
'.': ({name, birthDate, hobbies}) => new Person(name, birthDate, hobbies), // 👈 instance builder
birthDate: Date,
hobbies: {
'*': Hobby // 👈 we can refer a class decorated with #Reviver
}
})
class Person {
constructor(
public name: string,
public birthDate: Date,
public hobbies: Hobby[]
) {}
}
const person = new Person(
'Bob',
new Date('1998-10-21'),
[
new Hobby('programming', new Date('2021-01-01')),
new Hobby('cooking', new Date('2020-12-31')
]
);
const personJson = JSON.stringify(person);
const personReviver = Reviver.get(Person); // 👈 extract the reviver from the class
const personFromJson = JSON.parse(personJson, personReviver);

Categories

Resources