I have a store on Vuex with a socket listener.
This listener add to the state messages an array of array.
export const store = new Vuex.Store({
state: {
messages: []
},
mutations: {
SOCKET_GET_MESSAGES: (state, data) => {
state.messages[data[0].recipient] = data[0].res
// Data[0].recipient = the id of the recipient
// Data[0].res is an object with a login and a message.
},
}
In my console I can see the structure is correct if I do:
console.log(this.$store.state.messages)
with this output:
[__ob__: Observer]
2: Array(5)
> 0: {login: "w", message: "ABCD", id: 65}
> 1: {login: "w", message: "Deux", id: 66}
> 2: {login: "w", message: "Quatre", id: 67}
> 3: {login: "w", message: "J'envoie au deux", id: 69}
> 4: {login: "w", message: "Test", id: 70}
length: 5
__proto__: Array(0)
length: 3
__ob__: Observer {value: Array(3), dep: Dep, vmCount: 0}
__proto__: Array
But if I ask a specific ID I get undefined in my console log.
For example I ask for my first user with a message :
console.log(this.$store.state.messages[2])
Do you know how to solve this issue ?
I read lot of stuff on stackoverflow and on vuex documentation but I don't find an answer.
Thank you in advance for your help.
You're modifying the array directly, without using any actual method to do so. Therefore Vue cannot pick up the change you've done. You need to either use push or some other Vue helpers like $set. Quoting some helper docs:
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is syntax sugar for arr.splice(index, 1, value).
Also, here's a list of all supported mutation methods (wrapped by Vue):
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Thank you everyone,
After some research I want complete the answer of Andrey Popov.
Indeed if we use Vuex we need to use directly:
Vue.set(state.object, key, data)
The this.$set method is not available in Vuex.
It's now working with this method.
Related
I'm working with an API to consult car debits. If the car has more than one debit, the API returns an array of debits. If it has only one, it returns a single debit object (not an array with one element).
The problem is that I have to duplicate all the deserialization of this response checking whether the attribute is an array or a single object.
const debits = []
if (car.debits.length > 0) {
car.debits.forEach((debit: any) => {
debits.push({
id: uuidv1(),
description: debit.description,
label: debit.label,
amount: parseInt(debit.amount, 10)
})
})
} else {
debits.push({
id: uuidv1(),
description: debit.description,
label: debit.label,
amount: parseInt(debit.amount, 10)
})
}
Is there any way to simplify this? I showed just a small example but the object is much larger and there are many other attributes that I have to do the same.
If you have control over the API, you should probably have it return an array with a single element. If not, at the start of the function just force it into an array.
car.debits = car.debits.length ? car.debits : [car.debits]
If car.debits.length is undefined, which means it is not an array, you create an array and put the object inside it
I used to use HMSET to store an object after flattening it using the npm flat module. So something like this:
let bio = {
name: {
first: 'John',
last: 'Doe'
},
goal: {
desc: 'TO BE THE VERY BEST, LIKE NO ONE EVER WAS!',
test: 'TO CATCH THEM IS MY REAL TEST -- ',
cause: 'TO TRAIN THEM IS MY CAUUUUUSE!'
},
hobbies: [ 'making coffee', 'making low carb recipes', 'soccer' ],
'education.college': {
name: 'Baruch'
},
'hobbiesAsObject[]': {
'0': 'making coffee',
'1': 'making low carb recipes',
sport: 'Baseball'
},
age: 44
};
let flatBio = flat(bio);
let hmSetAsyncBio = await client.hmsetAsync('bio', flatBio);
But I read that HMSET is considered deprecated, When I try to use HSET in this same fashion it does not work. I know I can just JSON.stringfy it and store that using just a normal SET but I'd like to be able to increment fields in my object. This is how I did it previously:
const incrAge = await client.hincrbyAsync('bio', 'age', 1);
So my question is, how can I do this using HSET? I keep trying different things but it doesn't seem to be working. Some examples I have seen show looping through the object (this is one example I saw https://thisdavej.com/guides/redis-node/node/hashes.html) and using HSET on each item, but I have nested objects and it seems like it's more of a PITA now than it previously was. There has to be a simpler way to do it no?
Redis server version: 5.0.7
Node Version: 12.14.0
node-redis (npm module) version: 0.1.7
redis (npm module) version: 3.0.2
Error when I replace
let hmSetAsyncBio = await client.hmsetAsync('bio', flatBio);
with
let hmSetAsyncBio = await client.hsetAsync('bio', flatBio);
Error: node_redis: The HSET command contains a invalid argument type.
Only strings, dates and buffers are accepted. Please update your code to use valid argument types.
I was thinking about ways of implementing graphql response that would contain both an error and data.
Is it possible to do so without creating a type that would contain error?
e.g.
Mutation addMembersToTeam(membersIds: [ID!]! teamId: ID!): [Member] adds members to some team. Suppose this mutation is called with the following membersIds: [1, 2, 3].
Members with ids 1 and 2 are already in the team, so an error must be thrown that these members cannot be added, but member with an id 3 should be added as he is not in the team.
I was thinking about using formatResponse but seems that I can't get an error there.
Is it possible to solve this problem without adding error field to the return type?
Is it possible to solve this problem without adding error field to the return type?
Unfortunately, no.
A resolver can either return data, or return null and throw an error. It cannot do both. To clarify, it is possible to get a partial response and some errors. A simple example:
const typeDefs = `
type Query {
foo: Foo
}
type Foo {
a: String
b: String
}
`
const resolvers = {
Query: {
foo: () => {},
}
Foo: {
a: () => 'A',
b: () => new Error('Oops!'),
}
}
In this example, querying both fields on foo will result in the following response:
{
"data": {
"foo": {
"a": "A",
"b": null
}
},
"errors": [
{
"message": "Oops",
"locations": [
{
"line": 6,
"column": 5
}
],
"path": [
"foo",
"b"
]
}
]
}
In this way, it's possible to send back both data and errors. But you cannot do so for the same field, like in your question. There's a couple of ways around this. As you point out, you could return the errors as part of the response, which is usually how this is done. You could then use formatResponse, walk the resulting data, extract any errors and combine them with them with any other GraphQL errors. Not optimal, but it may get you the behavior you're looking for.
Another alternative is to modify the mutation so it takes a single memberId. You can then request a separate mutation for each id you're adding:
add1: addMemberToTeam(memberId: $memberId1 teamId: $teamId): {
id
}
add2: addMemberToTeam(memberId: $memberId2 teamId: $teamId): {
id
}
add3: addMemberToTeam(memberId: $memberId3 teamId: $teamId): {
id
}
This can be trickier to handle client-side, and is of course less efficient, but again might get you the expected behavior.
If you think about combining the GraphQL error - there is a way to do it in Apollo.
You need to set errorPolicy to all. That will help you notify users about the error and at the same time have as much data as possible.
none: This is the default policy to match how Apollo Client 1.0
worked. Any GraphQL Errors are treated the same as network errors and
any data is ignored from the response.
ignore: Ignore allows you to
read any data that is returned alongside GraphQL Errors, but doesn’t
save the errors or report them to your UI.
all: Using the all policy
is the best way to notify your users of potential issues while still
showing as much data as possible from your server. It saves both data
and errors into the Apollo Cache so your UI can use them.
But according to best practices, you shouldn't manipulate it in this way.
This is a great article about handling errors in GraphQL.
So, preferable way is to add "errors" field as part of your response and handle it in JS code.
We can achieve this by using a union. I would recommend visiting the great article Handling GraphQL errors like a champ
Example:
Mutation part: We can return the union type for the response & capture the result according to types.
type MemberType {
id: ID!
name: String!
}
enum ErrorType {
BAD_REQUEST_ERROR
FORBIDDEN_ERROR
INTERNAL_SERVER_ERROR
NOT_FOUND_ERROR
UNAUTHORIZED_ERROR
}
type GraphqlError {
type: ErrorType!
code: String!
message: String!
helpLink: URL
}
union UserRegisterResult = MemberType | GraphqlError;
addMembersToTeam(membersIds: [ID!]! teamId: ID!): UserRegisterResult!
Response:
addMembersToTeam(membersIds: [ID!]! teamId: ID!): {
...on MemberType{
id,
name,
}
...on GraphqlError{
id,
message,
statusCode,
}
}
I'm using map with a list of Cheerio results to return an attribute value. What I want is a variable that contains a list of attribute values (in this case ID's), but instead I'm getting the ID's and extra data.
The following code prints a list of ID's:
let ids = $('[data-profileid]').map(function() {
console.log($(this).attr('data-profileid'))
})
Result:
1012938412
493240324
123948532
423948234
...
But, the following code returns the IDs but in a different format:
let ids = $('[data-profileid]').map(function() {
return $(this).attr('data-profileid')
})
console.log(ids)
Results:
...
'69': '234234234,
'70': '9328402397432',
'71': '1324235234',
options:
{ withDomLvl1: true,
normalizeWhitespace: false,
xmlMode: false,
decodeEntities: true },
_root:
{ '0':
{ type: 'root',
name: 'root',
attribs: {},
...
What is all this extra data? It certainly isn't required. I'd rather just have an ordinary array.
According to http://api.jquery.com/map/:
As the return value is a jQuery object, which contains an array, it's
very common to call .get() on the result to work with a basic array.
So it looks like this should work:
let ids = $('[data-profileid]').map(function() {
return $(this).attr('data-profileid')
}).get()
What is all this extra data? It certainly isn't required. I'd rather just have an ordinary array.
Cheerio has a fluent API, meaning most of its functions return an object on which additional functions can be chained. If map just returned an "ordinary array" then you wouldn't be able to call additional Cheerio functions on the result. There aren't a lot of ways you can chain additional function calls onto the result of your map call, which returns an array of strings, but Cheerio's developers (taking a cue from jQuery's developers) chose to keep a consistent API rather than pepper it with special cases.
If you want an ordinary array, though, Cheerio gives you a handy toArray function:
let ids = $('[data-profileid]').map(function() {
return $(this).attr('data-profileid')
});
console.log(ids.toArray());
// => [ '1012938412', '493240324', '123948532', '423948234' ]
I have an object array in a reducer that looks like this:
[
{id:1, name:Mark, email:mark#email.com},
{id:2, name:Paul, email:paul#gmail.com},
{id:3,name:sally, email:sally#email.com}
]
Below is my reducer. So far, I can add a new object to the currentPeople reducer via the following:
const INITIAL_STATE = { currentPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_PERSON:
return {...state, currentPeople: [ ...state.currentPeople, action.payload]};
}
return state;
}
But here is where I'm stuck. Can I UPDATE a person via the reducer using lodash?
If I sent an action payload that looked like this:
{id:1, name:Eric, email:Eric#email.com}
Would I be able to replace the object with the id of 1 with the new fields?
Yes you can absolutely update an object in an array like you want to. And you don't need to change your data structure if you don't want to. You could add a case like this to your reducer:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => {
if (person.id === action.payload.id) {
return action.payload;
}
return person;
}),
};
This can be be shortened as well, using implicit returns and a ternary:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => (person.id === action.payload.id) ? action.payload : person),
};
Mihir's idea about mapping your data to an object with normalizr is certainly a possibility and technically it'd be faster to update the user with the reference instead of doing the loop (after initial mapping was done). But if you want to keep your data structure, this approach will work.
Also, mapping like this is just one of many ways to update the object, and requires browser support for Array.prototype.map(). You could use lodash indexOf() to find the index of the user you want (this is nice because it breaks the loop when it succeeds instead of just continuing as the .map would do), once you have the index you could overwrite the object directly using it's index. Make sure you don't mutate the redux state though, you'll need to be working on a clone if you want to assign like this: clonedArray[foundIndex] = action.payload;.
This is a good candidate for data normalization. You can effectively replace your data with the new one, if you normalize the data before storing it in your state tree.
This example is straight from Normalizr.
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
Can be normalized this way-
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
What's the advantage of normalization?
You get to extract the exact part of your state tree that you want.
For instance- You have an array of objects containing information about the articles. If you want to select a particular object from that array, you'll have to iterate through entire array. Worst case is that the desired object is not present in the array. To overcome this, we normalize the data.
To normalize the data, store the unique identifiers of each object in a separate array. Let's call that array as results.
result: [1, 2, 3 ..]
And transform the array of objects into an object with keys as the id(See the second snippet). Call that object as entities.
Ultimately, to access the object with id 1, simply do this- entities.articles["1"].
If you want to replace the old data with new data, you can do this-
entities.articles["1"] = newObj;
Use native splice method of array:
/*Find item index using lodash*/
var index = _.indexOf(currentPeople, _.find(currentPeople, {id: 1}));
/*Replace item at index using splice*/
arr.splice(index, 1, {id:1, name:'Mark', email:'mark#email.com'});