Ember.js - Accessing nested data via serializer - serialization

What is the best approach for accessing a single nested record in Ember?
The JSON response which we are trying to manipulate looks gets returned as the following: (the attribute being targeted is the tradeIdentifier property)
trade:
tradeIdentifier:"83f3f561-62af-11e7-958b-028c04d7e8f9"
tradeName:"Plumber"
userEmail:"test#gmail.com"
The project-user model looks partially like:
email: attr('string'),
trade:attr(),
tradeId: attr(),
The project-user serializer looks partially like:
export default UndefinedOmitted.extend(EmbeddedRecordsMixin, {
primaryKey: 'userRoleId',
attrs: {
'email': { key: 'userEmail' },
'trade': { key: 'trade' },
'tradeId': { key: 'tradeIdentifier' },
},
});
The trade attr here is a placeholder to make sure that the data was accessible.
I would like to be able to access the tradeIdentifier without having to do the following in the component:
const trade = get(formRole, 'trade');
if (trade) {
set(formProps, 'tradeId', trade.tradeIdentifier);
}
Have tested creating a trade-id transform (referenced via tradeId: attr('trade-id')), however to no avail.
export default Transform.extend({
deserialize(val) {
const trade = val;
const tradeId = val.tradeIdentifier;
return tradeId;
},
serialize(val) {
return val;
},
});
Can anyone suggest where I'm going wrong?

A transform seems a bit overkill for what I'm trying to achieve here, however it does the job. Managed to get it working by modifying the following:
In serializers/project-user.js:
'tradeId': { key: 'trade' },
Note that this references the property in the payload to transform, not the property being targeted (which was my mistake).
In models/project-user.js:
tradeId: attr('trade-id'),
Attribute references the transform.
In transform/trade-id.js:
export default Transform.extend({
deserialize(val) {
let tradeId = val
if (tradeId) {
tradeId = val.tradeIdentifier;
}
return tradeId;
},
serialize(val) {
return val;
},
});
If there's a simpler solution outside of transforms, I would still be open to suggestions.

Related

Where should I use computed and methods in Vue js? (need proper guideline)

Look at the image below and please explain where should I use computed instead of methods and vice versa? It confuses me.
As a rule of thumb: a computed is a simple getter (though they can be setters, but that's not something you'd typically use) that is dependent on one or more properties. It'll update automatically when those properties change. You cannot pass it parameters. You would use a method when you need to pass a parameter and/or need to perform an action or mutation.
data() {
firstName: 'Bert',
lastName: 'Ernie'
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
This will return "Bert Ernie" and will update automatically when either firstName or lastName change.
Now if you need to change something, or for example select something from a list using a parameter, you would use a method.
data() {
users: [
{ id: 1, name: 'Bert' }.
{ id: 2, name: 'Ernie' }
]
},
methods: {
getUser(userid) {
return this.users.find(user => user.id === userid);
},
setUserName(userid, newName) {
const user = this.users.find(user => user.id === userid);
if (user) {
user.name = newName;
}
}
}

How to structure vuex store for list of objects and mapping to them individually based on ID

My photo app has a list of photoshoot sessions, where each session contains metadata such as date, time, and many images.
I wrote a Vuex module called session that stores all the info for ONE session.
const state = {
images: [],
date: null,
photographerId: null
}
However, realistically I would have multiple sessions, each with their own metadata and images. I read in many places that I should create a normalized store. So what I envision that to be like is:
const state = {
sessions: {
session1: {
images: [],
date: null,
photographerId: null
},
session2: {
images: [],
date: null,
photographerId: null
},
...
}
}
To access each session, I could have a getter like:
const getters = {
sessionById: (state) => (sessionId) => {
return state.sessions[sessionId];
}
}
But here comes the problem. In my current page for showing data about a specific session, I have code like:
computed: {
...mapState('session', ['images', 'date', 'photographerId'])
}
If I was to move to a normalized store, how can I still achieve this? I know I can map the getter instead and declare each session's attribute individually like:
computed: {
...mapGetters('session', ['sessionById']),
images() {
return this.sessionById(sessionId).images;
},
date() {
return this.sessionById(sessionId).date;
},
photographerId() {
return this.sessionById(sessionId).photographerId;
}
}
But that feels pretty hacky and worse than what I had before with mapState.
I have a gut feeling I'm using Vuex wrongly, could anyone please enlighten me?

Ember: Must include an 'id' in an object passed to 'push'

Currently experiencing the following error: You must include an 'id' for failed-shotlist in an object passed to 'push'. This is in a code base I have inherited mid-development and I am fairly new to Ember.
From what I understand, this occurs when the backend does not respond with an ID. The server payload looks like the following (returning an alert object with an embedded failedShotlist record):
alertAuthor: "Test name"
alertDate:"2018-06-28T16:25:21+12:00"
alertIdentifier:"456e15c7-7a8b-11e8-84a8-06f4aef780e3"
alertType:"failedShotlist"
email:"test#gmail.com"
failedShotlist:
projectIdentifier:"79050dfb-5faf-11e8-84a8-06f4aef780e3"
projectName:"8888 st"
projectRoleENUM:"bp"
projectRoleName:"Building Participant"
shotlistDescription:"Framing"
shotlistIdentifier:"79d52773-5faf-11e8-84a8-06f4aef780e3"
inviteIdentifier:null
profileId:"c4e02bee-3d26-11e8-84a8-06f4aef780e3"
shotlistIdentifier:"79d52773-5faf-11e8-84a8-06f4aef780e3"
Since the backend doesn't respond with an ID attr, the primary key needs to be transformed using a serializer's 'primaryKey' property:
serializers/alert.js
export default ApplicationSerializer.extend(EmbeddedRecordsMixin, {
primaryKey: 'alertIdentifier',
attrs: {
'invite': { deserialize: 'records' },
'failedShotlist': { deserialize: 'records' },
},
});
I couldn't find any mention of this, but I assume that embedded records are further serialized by their own serializers. The existing one is as follows:
serializers/failedShotlist.js
export default ApplicationSerializer.extend({
attrs: {
'shotlistId': { key: 'shotlistIdentifier' },
'projectId': { key: 'projectIdentifier' },
},
});
Since the ID's for the failedShotlist object also need to be transformed, I have updated this to include the primaryKey prop:
serializers/failedShotlist.js
export default ApplicationSerializer.extend({
primaryKey: 'shotlistIdentifier',
attrs: {
'shotlistId': { key: 'shotlistIdentifier' },
'projectId': { key: 'projectIdentifier' },
},
});
Unfortunately, this results in the same error I originally encountered. Any ideas as to how this might be resolved?
Something I had overlooked was that the source files for the adapter and the serializer weren't following the naming convention of the rest of the codebase.
Where the serializer was called failedShotlist.js, the model related to it was called failed-shotlist.js.
Renaming the serializer file to failed-shotlist.js allowed my existing code to work:
export default ApplicationSerializer.extend({
primaryKey: 'shotlistIdentifier'
}

How to update an object in 'state' with react redux?

In my reducer, suppose originally I have this state:
{
"loading": false,
"data": {
"-L1LwSwW97KkwdSnYvsc": {
"likeCount": 10,
"liked": false, // I want to update this property
"commentCount": 5
},
"-L1EY2_fqzn7sM1Mbf_F": {
"likeCount": 8,
"liked": true,
"commentCount": 22
}
}
}
Now, I want to update liked property inside -L1LwSwW97KkwdSnYvsc object, which is inside data object and make it true. This is what I've been trying, but apparently, it's wrong, because after I've updated the state, the componentWillReceiveProps function inside a component that listens to the state change does not get triggered:
var { data } = state;
data['-L1LwSwW97KkwdSnYvsc'].liked = !data['-L1LwSwW97KkwdSnYvsc'].liked;
return { ...state, data };
Could you please specify why it's wrong and how I should change it to make it work?
You're mutating state! When you destructure:
var { data } = state;
It's the same as:
var data = state.data;
So when you do:
data[…].liked = !data[…].liked
You're still modifying state.data which is in turn mutating state. That's never good - use some nested spread syntax:
return {
...state,
data: {
...state.data,
'-L1LwSwW97KkwdSnYvsc': {
...state.data['-L1LwSwW97KkwdSnYvsc'],
liked: !state.data['-L1LwSwW97KkwdSnYvsc'].liked
}
}
};
Using spread operator is good until you start working with deeply nested state and/or arrays(remember spread operator does a shallow copy only).
I would rather recommend you starting working with immutability-helper instead. It is a React recommendation and it will let your code more readable and bug free.
Example:
import update from "immutability-helper";
(...)
const toggleLike = !state.data["-L1LwSwW97KkwdSnYvsc"].liked
return update(state, {
data: {
"-L1LwSwW97KkwdSnYvsc": {
like: {
$set: toggleLike
}
}
}
})

Validation of fetched data from API Redux React

So, I will go straight to the point. I am getting such data from api:
[
{
id: 123,
email: asd#asd.com
},
{
id: 456,
email: asdasd.com
},
{
id: 789,
email: asd#asd
},
...
]
and I should validate email and show this all info in a list, something like this:
asd#asd.com - valid
asdasd.com - invalid
asd#asd - invalid
...
My question is what is the best way to store validation data in a store? Is it better to have something like "isValid" property by each email? I mean like this:
store = {
emailsById: [
123: {
value: asd#asd.com,
isValid: true
},
456: {
value: asdasd.com,
isValid: false
},
789: {
value: asd#asd,
isValid: false
}
...
]
}
or something like this:
store = {
emailsById: [
123: {
value: asd#asd.com
},
456: {
value: asdasd.com
},
789: {
value: asd#asd
}
...
],
inValidIds: ['456', '789']
}
which one is better? Or maybe there is some another better way to have such data in store? Have in mind that there can be thousands emails in a list :)
Thanks in advance for the answers ;)
I recommend reading the article "Avoiding Accidental Complexity When Structuring Your App State" by Tal Kol which answers exactly your problem: https://hackernoon.com/avoiding-accidental-complexity-when-structuring-your-app-state-6e6d22ad5e2a
Your example is quite simplistic and everything really depends on your needs but personally I would go with something like this (based on linked article):
var store = {
emailsById: {
123: {
value: '123#example.com',
},
456: {
value: '456#example.com',
},
789: {
value: '789#example.com',
},
// ...
},
validEmailsMap: {
456: true, // true when valid
789: false, // false when invalid
},
};
So your best option would be to create a separate file that will contain all your validations methods. Import that into the component you're using and then when you want to use the logic for valid/invalid.
If its something that you feel you want to put in the store from the beginning and the data will never be in a transient state you could parse your DTO through an array map in your reducer when you get the response from your API.
export default function (state = initialState, action) {
const {type, response} = action
switch (type) {
case DATA_RECIEVED_SUCCESS:
const items = []
for (var i = 0; i < response.emailsById.length; i++) {
var email = response.emailsById[i];
email.isValid = checkEmailValid(email)
items.push(email)
}
return {
...state,
items
}
}
}
However my preference would be to always check at the last moment you need to. It makes it a safer design in case you find you need to change you design in the future. Also separating the validation logic out will make it more testable
First of all, the way you defined an array in javascript is wrong.
What you need is an array of objects like,
emails : [
{
id: '1',
email: 'abc#abc.com',
isValid: true
},
{
id: '2',
email: 'abc.com',
isValid: false;
}
];
if you need do access email based on an id, you can add an id property along with email and isValid. uuid is a good way to go about it.
In conclusion, it depends upon your use case.
I believe, the above example is a good way to keep data in store because it's simple.
What you described in your second example is like maintaining two different states. I would not recommend that.