How to load mobx state from json? - javascript

I want to store my mobx state in browser localStorage, so, if i use this approach https://stackoverflow.com/a/40326316
I save store with toJS, but don't know how to apply it. With extendObservable I get following error Error: [mobx] 'extendObservable' can only be used to introduce new properties. Use 'set' or 'decorate' instead
Thanks in advance.
My approach is:
class MyStore {
...
public async load() {
const cached = await browser.storage.local.get("cache");
const data = JSON.parse(cached["cached"]);
Object.keys(data).forEach(x => {
(this as any)[x] = (data as any)[x];
});
...
}
But i think this is anitpattern.

Are you sure extendObservable doesn't work.
I've used something like this in my code.
class MyStore {
async load() {
const cached = await browser.storage.local.get("cache");
mobx.extendObservable(this, cached);
}
}
Edit:
This seems to be not working, you need to access the properties after extendObservable in order to reload them, you could use autorun but just use another method.
I've implemented load function based on a simple forEach;
Try the following.
load = async () => {
const { cache } = await JSON.parse(localStorage.getItem("cache"));
Object.keys(cache).forEach(key => {
this[key] = cache[key];
});
};
CodeSandbox
https://codesandbox.io/s/late-snow-xppx0?ontsize=14&hidenavigation=1&theme=dark

If you have a class, and "raw" json data, what i'm doing is to accept raw data in the constructor & then update the class properties.
For example, my raw data looks like this:
{
users: [
{ id: 1, firstName: 'foo', lastName: 'bar', customer: { id: 1, name: 'bla' } },
{ id: 2, firstName: 'foo2', lastName: 'bar2', customer: { id: 2, name: 'customer' } },
];
}
class User {
id;
#observable firstName;
#observable lastName;
customer;
constructor(rawUser) {
this.id = rawUser.id;
this.firstName = rawUser.firstName;
this.lastName = rawUser.lastName;
this.customer = new Customer(rawUser.customer);
}
}
class UsersStore {
#observable users = [];
constructor(rawUsers) {
this.users = rawUsers.map(rawUser => new User(rawUser));
}
}
Then when I'm restoring the data I'm just using
const usersStore = new UsersStore(rawData.users);
The cool thing in this approach is the nesting handling, each "level" handles its part.

Related

How to handle the JSON object which lack of some information?

I am using React with nextJS to do web developer,I want to render a list on my web page, the list information comes from the server(I use axios get function to get the information). However some JSON objects are lack of some information like the name, address and so on. My solution is to use a If- else to handle different kind of JSON object. Here is my code:
getPatientList(currentPage).then((res: any) => {
console.log("Response in ini: " , res);
//console.log(res[0].resource.name[0].given[0]);
const data: any = [];
res.map((patient: any) => {
if ("name" in patient.resource) {
let info = {
id: patient.resource.id,
//name:"test",
name: patient.resource.name[0].given[0],
birthDate: patient.resource.birthDate,
gender: patient.resource.gender,
};
data.push(info);
} else {
let info = {
id: patient.resource.id,
name: "Unknow",
//name: patient.resource.name[0].given[0],
birthDate: patient.resource.birthDate,
gender: patient.resource.gender,
};
data.push(info);
}
});
Is there any more clever of efficient way to solve this problem? I am new to TS and React
Use the conditional operator instead to alternate between the possible names. You should also return directly from the .map callback instead of pushing to an outside variable.
getPatientList(currentPage).then((res) => {
const mapped = res.map(({ resource }) => ({
id: resource.id,
// want to correct the spelling below?
name: "name" in resource ? resource.name[0].given[0] : "Unknow",
birthDate: resource.birthDate,
gender: resource.gender,
}));
// do other stuff with mapped
})

Assign dynamically nested array of classes

I need to be able to receive data from an external API and map it dynamically to classes. When the data is plain object, a simple Object.assign do the job, but when there's nested objects you need to call Object.assign to all nested objects.
The approach which I used was to create a recursive function, but I stumble in this case where there's a nested array of objects.
Classes
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
}
Function to initialize a class
function create(instance: object, data: any) {
for (const [key, value] of Object.entries(instance)) {
if (Array.isArray(value)) {
for (const element of data[key]) {
// get the type of the element in array dynamically
const newElement = new User();
create(newElement, element)
value.push(newElement);
}
} else if (typeof value === 'object') {
create(value, data[key]);
}
Object.assign(value, data);
}
}
const orgWithError = Object.assign(new Organization(), { admin: { id: 'admin-external' }});
console.log(orgWithError.admin.getFullName()); // orgWithError.admin.getFullName is not a function
const org = new Organization();
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
create(org, data);
// this case works because I manually initialize the user in the create function
// but I need this function to be generic to any class
console.log(org.users[0].getFullName()); // "name surname"
Initially I was trying to first scan the classes and map it and then do the assign, but the problem with the array of object would happen anyway I think.
As far as I understand from your code, what you basically want to do is, given an object, determine, what class it is supposed to represent: Organization, Account or User.
So you need a way to distinguish between different kinds of objects in some way. One option may be to add a type field to the API response, but this will only work if you have access to the API code, which you apparently don't. Another option would be to check if an object has some fields that are unique to the class it represents, like admin for Organization or account for User. But it seems like your API response doesn't always contain all the fields that the class does, so this might also not work.
So why do you need this distinction in the first place? It seems like the only kind of array that your API may send is array of users, so you could just stick to what you have now, anyway there are no other arrays that may show up.
Also a solution that I find more logical is not to depend on Object.assign to just assign all properties somehow by itself, but to do it manually, maybe create a factory function, like I did in the code below. That approach gives you more control, also you can perform some validation in these factory methods, in case you will need it
class Organization {
id = 'org1';
admin = new User();
users: User[] = [];
static fromApiResponse(data: any) {
const org = new Organization()
if(data.id) org.id = data.id
if(data.admin) org.admin = User.fromApiResponse(data.admin)
if(data.users) {
this.users = org.users.map(user => User.fromApiResponse(user))
}
return org
}
}
class User {
id = 'user1';
name = 'name';
account = new Account();
getFullName() {
return `${this.name} surname`;
}
static fromApiResponse(data: any) {
const user = new User()
if(data.id) user.id = data.id
if(data.name) user.name = data.name
if(data.account)
user.account = Account.fromApiResponse(data.account)
return user
}
}
class Account {
id = 'account1';
money = 10;
calculate() {
return 10 * 2;
}
static fromApiResponse(data: any) {
const acc = new Account()
if(data.id) acc.id = data.id
if(data.money) acc.money = data.money
return acc
}
}
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const organization = Organization.fromApiResponse(data)
I can't conceive of a way to do this generically without any configuration. But I can come up with a way to do this using a configuration object that looks like this:
{
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
}
and a pointer to the root node, 'org'.
The keys of this object are simple handles for your type/subtypes. Each one is mapped to an object that has a _ctor property pointing to a constructor function, and a collection of other properties that are the names of members of your object and matching properties of your input. Those then are references to other handles. For an array, the handle is [surrounded by square brackets].
Here's an implementation of this idea:
const create = (root, config) => (data, {_ctor, ...keys} = config [root]) =>
Object.assign (new _ctor (), Object .fromEntries (Object .entries (data) .map (
([k, v]) =>
k in keys
? [k, /^\[.*\]$/ .test (keys [k])
? v .map (o => create (keys [k] .slice (1, -1), config) (o))
: create (keys [k], config) (v)
]
: [k, v]
)))
class Organization {
constructor () { this.id = 'org1'; this.admin = new User(); this.users = [] }
}
class User {
constructor () { this.id = 'user1'; this.name = 'name'; this.account = new Account() }
getFullName () { return `${this.name} surname`}
}
class Account {
constructor () { this.id = 'account1'; this.money = 10 }
calculate () { return 10 * 2 }
}
const createOrganization = create ('org', {
org: { _ctor: Organization, admin: 'usr', users: '[usr]' },
usr: { _ctor: User, account: 'acct' },
acct: { _ctor: Account }
})
const orgWithoutError = createOrganization ({ admin: { id: 'admin-external' }});
console .log (orgWithoutError .admin .getFullName ()) // has the right properties
const data = { id: 'org2', admin: { id: 'admin2' }, users: [ { id: 'user-inside' }]}
const org = createOrganization (data)
console .log (org .users [0] .getFullName ()) // has the right properties
console .log ([
org .constructor .name,
org .admin .constructor.name, // has the correct hierarchy
org .users [0]. account. constructor .name
] .join (', '))
console .log (org) // entire object is correct
.as-console-wrapper {min-height: 100% !important; top: 0}
The main function, create, receives the name of the root node and such a configuration object. It returns a function which takes a plain JS object and hydrates it into your Object structure. Note that it doesn't require you to pre-construct the objects as does your attempt. All the calling of constructors is done internally to the function.
I'm not much of a Typescript user, and I don't have a clue about how to type such a function, or whether TS is even capable of doing so. (I think there's a reasonable chance that it is not.)
There are many ways that this might be expanded, if needed. We might want to allow for property names that vary between your input structure and the object member name, or we might want to allow other collection types besides arrays. If so, we probably would need a somewhat more sophisticated configuration structure, perhaps something like this:
{
org: { _ctor: Organization, admin: {type: 'usr'}, users: {type: Array, itemType: 'usr'} },
usr: { _ctor: User, account: {type: 'acct', renameTo: 'clientAcct'} },
acct: { _ctor: Account }
}
But that's for another day.
It's not clear whether this approach even comes close to meeting your needs, but it was an interesting problem to consider.

Can't set this nested object in a immutable record correctly

I have no problem setting flat object immutable records. But now I have a problem where I would like to setup a record, I guess maybe a list of records? I'm trying to get my User ref in my firebase database into a immutable record, or records.
So I load my users ref from firebase. It's structure looks like.
I can't figure out how to nest with lists. User has a list of UID's. From there it gets much flatter. But is this even possible to have a List of Records? I haven't seen any examples online so I'm just lost at this point. Any help would be great appreciated.
If I was doing something flatter like just profile it would look like.
export const Profile = Record({
key: null,
profileName: '',
profilePic: '',
teamName: '',
});
It seems getting past the list of UID's is the first challenge Im having a hard time getting past. Once past that initial list it is much flatter.
Maybe I shouldn't even be doing this immutable. Im open to other suggesting for what to do with this data. Im just used to using Immutable records in my application at this point. Was hoping to stick to that pattern for this last bit I have.
Basically there is no list of records as of yet. From what I could find you could use map with record values and extend Record class as per this answer to get equivalent to what you want(the code below is based on this approach).
I also could find an interesting article that had multiple approaches, for the same idea in a functional wayhere.
If this is not exactly what you were expecting let me know.
var users = {
'efbluh': {
'profile': {
profileName: 'bill',
profilePic: 'https://blue',
teamName: 'locals',
},
'team': {
'Lpphasd' : {
competitorKey: 'a'
}
}
},
'fixbluh': {
'profile': {
profileName: 'bill',
profilePic: 'https://blue',
teamName: 'locals',
},
'team': {
'Lpphasd' : {
competitorKey: 'a'
},
'asdsadasda' : {
competitorKey: 'b'
}
}
}
};
var ProfileRecord = Immutable.Record({
profileName: '',
profilePic: '',
teamName: '',
});
var teamRecord = Immutable.Record({
competitorKey: ''
});
class User extends Immutable.Record({'profile': new ProfileRecord(), 'team':Immutable.Map()}) {
constructor({profile, team} = {}) {
super({team: Immutable.Map(team).map(x=>new teamRecord(x)), profile: new ProfileRecord(profile)})
}
}
var userMap = Immutable.Map(users).map(x=>new User(x));
// Verify that it's really a record
console.log(userMap.get('efbluh').get('profile').profileName)
console.log(userMap.get('fixbluh').toJS())
console.log(userMap.get('fixbluh').get('team').toJS())
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.7.4/immutable.min.js"></script>
Shyams suggestion was absolutely on point so it's the accepted answer. Most people should be able to adopt that if you have a need. However if you are like me, and are using firebase with redux and selectors here is how Shyams suggestion worked for me. Im not claiming it to be the most pure example of react/redux/selectors you've ever seen but it's working for me.
Without further ado.
Initially I get my Users from a redux action and reducer.
My action and action creators to fetch the data from firebase and initially store it in a Key Value Immutable Record from some high level firebase db functions I have.
import * as types from './actionTypes';
import { Record } from 'immutable';
import { FirebaseList } from 'src/firebase';
export const League = new Record({
key: null,
value: null
})
export const leagueFireDB = new FirebaseList({
onLoad: loadLeagueSuccess,
},League);
export function loadLeague() {
console.log("LOADLEAGUE::");
return dispatch => {
leagueFireDB.path = `users/`;
leagueFireDB.subscribeChild(dispatch);
};
}
export function loadLeagueSuccess(league) {
console.log("LEAGUElOADSuccess::", league);
return {
type: types.LOAD_LEAGUE_SUCCESS,
payload: league
};
}
In the reducer, to get a proper return of all the deeply nested data to my connected component I used fromJS. Maybe there's a better way to do this but this worked for me.
import { Record, fromJS } from 'immutable';
import {
LOAD_LEAGUE_SUCCESS,
LOAD_LEAGUE_ERROR,
} from '../actions/actionTypes';
export const LeagueState = new Record({
key: null,
value: null
})
export function leagueReducer(state = new LeagueState(), {payload, type}) {
switch (type) {
case LOAD_LEAGUE_SUCCESS:
return state.merge(fromJS(payload));
default:
return state;
}
}
In my league page, a connected component, I have a selector wired in my mapstate to props
const mapStateToProps = (state, ownProps) => {
console.log("MYSTATE::",state)
return {
league: LeagueSelector(state),
}
}
Then in my selector is where I do the finishing move on Users data by nesting the records and maps specified in the accepted answer.
import { createSelector } from 'reselect';
import { Record, Map } from 'immutable';
var ProfileRecord = Record({
profileName: '',
profilePic: '',
teamName: '',
});
var teamRecord = Record({
competitorKey: ''
});
class User extends Record({'profile': new ProfileRecord(), 'team':Map()}) {
constructor({profile, team} = {}) {
super({team: Map(team).map(x=>new teamRecord(x)), profile: new ProfileRecord(profile)})
}
}
export function getLeague(state) {
return state
}
export function getLeagueList(state) {
return Map(state.league.value).map(x=>new User(x));
}
//=====================================
// MEMOIZED SELECTORS
//-------------------------------------
export const LeagueSelector = createSelector(
getLeague,
getLeagueList,
);
And here is the final proof of our neatly deeply nested immutable chaos. :)

ES6 destructuring: How do I create a new object that omits dynamically referenced keys

Is there an ES6 (and upwards) solution using destructuring and the spread operator to create a new object with a key and value deleted from the original object, when the key reference is dynamic, so:
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
const { [idToDelete], ...newState } = state // dynamic key
console.log('newState:', newState)
// desired newState would only have the key 12345 and its value
Unless it's my present Babel setup, I can't figure out the clean ES6 way of doing this (if it exists).
Many thanks in advance
when destructuring using dynamic id you need to set a var with the remove value : the doc about this
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
// the removed object will go to unusedVar
const { [idToDelete]: unusedVar, ...newState } = state // dynamic key
console.log('newState:', newState)
a better way if you don't need to keep the deleted object is to use the keyword delete
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
delete state[idToDelete]
console.log('newState:', state)
I don't think it's possible to cleanly achieve with ES6 destructuring. Since other answers include mutating the state, try this instead:
const state = {
12344: {
url: 'http://some-url.com',
id: '12344'
},
12345: {
url: 'http://some-other-url.com',
id: '12345'
}
}
const idToDelete = 12344
const newState = Object.assign({}, state);
delete newState[idToDelete];
console.log('newState:', newState)
console.log('old state:', state);

Using immutability-helper in React to set variable object key

I have a function I want to write in React. In my class I have a state object fields that looks like this:
this.state = {
step: 1,
fields: {
type: '',
name: '',
subtype: '',
team: '',
agreement: ''
}
};
I have various functions that assign those keys using immutability helper that generally look like:
assignType(attribute) {
var temp = update(this.state.fields, {
type: {$set: attribute}
});
this.setState({
fields: temp
});
}
What I would like to do is use a function that's more generic and do something like this:
assignAttribute(field, attribute) {
var temp = update(this.state.fields, {
field: {$set: attribute}
});
this.setState({
fields: temp
});
}
But, this doesn't work. What can I do to use a variable key using immutability-helper?
Figured it out! I needed to use ES6 computed property names and simply edit assignAttribute to:
assignAttribute(field, attribute) {
var temp = update(this.state.fields, {
[field]: {$set: attribute}
});
this.setState({
fields: temp
});
}
You can use the [] syntax if you have dynamic field names:
var data = {}
data[field] = {$set: attribute}
var temp = update(this.state.fields, data)
This gets a lot more concise if you can use ES6.

Categories