Vuex removes state id key value pair on mutation - javascript

My Vue app has the following state
saving: false,
saveStatus: 0,
validationErrors: [],
record: {
createdAt:"2017-04-22T16:17:14.825Z"
email:"isreyakenobi#starwars.com.au"
id:"BFCD7EC9-094D-4A97-BDFD-5C7D9D9F092A"
mobile:"12345678"
name:"Rey QuestionMark"
phone:"94801234"
type:"Customer"
updatedAt:"2017-04-25T18:38:21.101Z"
}
I have a component which fires the saveCustomer action when a user completed a form updating the customer's email.
saveCustomer = async ({ commit, state }, customer) => {
commit(types.SAVE_CUSTOMER)
const response = await api.put(
'customers',
customer
)
if (response instanceof Error) {
// If parameters passed are invalid
if (response.response.status === config.errorStatusCodes.badRequest) {
commit(types.SAVE_CUSTOMER_INVALID, Object.values(response.response.data.message))
return
}
commit(types.SAVE_CUSTOMER_FAILURE)
return
}
commit(types.CUSTOMER_SAVED, response.data)
commit(types.CANCEL_EDIT_CUSTOMER)
}
Upon the api returning a Bad Request response as so
{
"statusCode":"400",
"statusText":"Bad Request",
"name":"",
"message":{
"email":"\"email\" is not correctly formatted."
}
}
The SAVE_CUSTOMER_INVALID mutation does not touch the record in the state but rather mutates other aspects of the state.
[types.SAVE_CUSTOMER_INVALID] (state, errors) {
state.saving = false
state.saveStatus = config.saveStatuses.invalid
state.validationErrors = errors
}
For some reason the id key value pair is stripped from the record in the state when this mutation is committed. Every other key value pair is persisted.
Any ideas what's going on?

Related

Updating array in reducer

i am trying to update state in reducer with a array of objects. Following is what i am doing.
Action.js file
export const apiSuccess = (data, forCust) => {
return {
type: ACTIONS.validateSuccess,
data: data,
forCust
};
};
export const apiFailed = (error, forCust) => {
console.log('pandetail failed', error)
return {
type: ACTIONS.ValidateFailed,
data: error?.details?.data,
forCust
};
};
here forCust can be customer or admin
Here is the reducer
const initialState = {
otherDetails: {},
individualDetails: [{ forCust: 'customer' }, { forCust: 'admin' }],
};
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
case ACTIONS.validateSuccess:
console.log('Action in reducer', action)
debugger;
return {
...state,
otherDetails: action?.data,
individualDetails: state.individualDetails.map((item) => {
if(item.forCust === action.forCust){
item = action
}
return item
}),
}
case ACTIONS.validateFailed:
return {
...state,
otherDetails: action?.data,
individualDetails: state.individualDetails.map((item) => {
if (item.forCust === action.forCust) {
item = action
}
return item
}),
}
default:
return state;
}
};
export default reducer;
Now, when action is dispatched it dispatches with success and other parameter which is forCust to check if the dispatched action is for admin or customer. Now i have two fields in the same component but different parameters which calls the action(admin/customer). If the validateSuccess with customer is called i am trying to update customer details in individualDetails array and if admin is called i am trying to update only the admin state array object.
Now, what happens is that i am not able to maintain the old state ie when customer changes and admin details is there, it updated the whole array, not only the customer.
Also if somehow one of them get failed, failed action is dispatched and then update the array corresponding to the forCust field.
any help will be appreciated.

VUEX mutate array data each request

I have some data from an API that I am storing in VUEX and then displaying in the UI. On initial page load there is a request that pulls in the initial data and displays. All works well. My issues is When I now submit a form input for another request using an event handler I am just pushing to the array and it is adding to the array (which makes sense) and creates another instance below the current data which I do not want. Is there a way to actually CHANGE / MUTATE the current data that is in the array and update the UI with the new values?
STORE
import { createStore } from 'vuex';
import axios from 'axios';
export default createStore({
state: {
ipData: [],
currentIP: '',
},
mutations: {
SET_CURRENT_IP(state, currentIP) {
state.currentIP = currentIP;
},
SET_IP_DATA(state, ipData) {
state.ipData.push(ipData);
},
},
});
FORM SUBMIT
methods: {
async submitForm() {
const isFormValid = await this.v$.$validate();
if (!isFormValid) return;
axios
.get(`${this.url}${this.getIP}`, {
headers,
})
.then((response) => {
this.$store.commit('SET_IP_DATA', response.data);
})
.catch((error) => {
console.log(error.response);
});
},
},
VUEX OBJECT:
ipData:Array[1]
0:Object
as:Object
domains:Array[5]
ip:"8.8.8.8"
isp:"Google LLC"
location:Object
city:"Mountain View"
country:"US"
geonameId:5375480
lat:37.38605
lng:-122.08385
postalCode:"94035"
region:"California"
timezone:"-07:00"
If your ipData is array of object, you can create another mutation for updating your array (use id or some other identifier to match right object):
UPDATE_IP_DATA(state, payload) {
state.ipData = [
...state.ipData.map((item) =>
item.id !== payload.id
? item
: {
...item,
...payload,
}
),
];
}

Update graphql context in a mutation for the output object of the same mutation

I want to use a single mutation for the application to send user info to server and then get the top level query in the output. (I know this is not a good convention but I want to do this to test if I can improve performance).
So as a result, there will only be one mutation that takes user's info and returns the feed. This mutation updates the information about the user that is fetched in every query as the context of request. The context is used to generate personalized feed. However when I call this mutation, the output returned is calculated using old context. What I need to do is update the context for this same mutation too.
I put down a simplified version of the code to show what's happening:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
someData: {
type: GraphQLList(Post),
resolve: (user, args, context) => getFeed(context) // context in here is the old context.
},
})
})
const someMutation = mutationWithClientMutationId({
name: 'someMutation',
inputFields: {
location: { type: GraphQLString },
},
outputFields: {
user: {
type: UserType,
resolve: (source, args, context) => getUser(context.location),
},
},
mutateAndGetPayload: async (data, context) => {
updateUserInfo(data)
// I have tried updating context like this but it's not working.
context = { location: data.location }
return {
// I even tried putting user here like this:
// user: getUser(data.location)
// However, the resulting query fails when running getFeed(context)
// the context is still the old context
}
},
})
This is just how JavaScript works. You can reassign the value of a function parameter, but that will not change the value the function was passed.
function makeTrue (value) {
value = true
console.log(value) // true
}
var myVariable = false
makeTrue(myVariable)
console.log(myVariable) // false
If the value you pass to the function is an object or an array, you can mutate it and the original value will be mutated as well because objects and arrays in Javascript are passed by reference.
function makeItTrue (value) {
value.it = true
console.log(value.it) // true
}
var myVariable = { it: false }
makeTrue(myVariable)
console.log(myVariable.it) // true
In other words, you need to mutate the context parameter instead of reassigning it.

Using react-apollo (GraphQL) to do an insert mutation, how do I get the inserted id for the local store update?

I have form with the following submit handler, which then uses react-apollo's mutate method to send the GraphQL mutation:
const doSubmit = (values, { setSubmitting, setErrors }) => {
//create a new obj: can't submit the extra stuff in values, GraphQL will yell at us!
const newUser = {
firstName: values.firstName,
lastName: values.lastName,
emailAddress: values.emailAddress,
isActive: values.isActive,
sharedAccount: values.sharedAccount,
userId: values.userId
};
mutate({
variables: { user: newUser },
update: store => {
//only update if adding a new user
if (values.userId !== 0) {
return;
}
let data = store.readQuery({ query: USER_LIST_QUERY });
data.users.push(newUser);
store.writeQuery({ query: USER_LIST_QUERY, data });
}
}).then(() => {
//let formik know that submit is complete
setSubmitting(false);
//todo: redirect to userlist
});
};
This approach is based on this mutate-with-update example in the docs.
I know that the mutate promise handler will have access to the inserted id because it will be returned in the graphql response, but that doesn't seem to be in time.
Unless I could have access to the store from that promise handler too, I don't see how this is possible. But it seems like such a common use case that there has to be a way, right? Am I missing something obvious?
The update method provides the updated object as the second parameter
update: (store, { ... access userId here ... }) => { ... }
From the docs:
This function will be called twice over the lifecycle of a mutation. Once at the very beginning if an optimisticResponse was provided. The writes created from the optimistic data will be rolled back before the second time this function is called which is when the mutation has succesfully resolved. At that point update will be called with the actual mutation result and those writes will not be rolled back.

Issue with automatic UI updates in Apollo: `updateQuery` not working properly with `subscribeToMore`

I'm using GraphQL subscriptions for my chat application, but I have an issue with the UI updates.
Here are the query and the mutation I'm using:
const createMessage = gql`
mutation createMessage($text: String!, $sentById: ID!) {
createMessage(text: $text, sentById: $sentById) {
id
text
}
}
`
const allMessages = gql`
query allMessages {
allMessages {
id
text
createdAt
sentBy {
name
location {
latitude
longitude
}
}
}
}
`
Then, when exporting, I'm wrapping my Chat component like so:
export default graphql(createMessage, {name : 'createMessageMutation'})(
graphql(allMessages, {name: 'allMessagesQuery'})(Chat)
)
I'm subscribing to the allMessagesQuery in componentDidMount:
componentDidMount() {
// Subscribe to `CREATED`-mutations
this.createMessageSubscription = this.props.allMessagesQuery.subscribeToMore({
document: gql`
subscription {
Message(filter: {
mutation_in: [CREATED]
}) {
node {
id
text
createdAt
sentBy {
name
}
}
}
}
`,
updateQuery: (previousState, {subscriptionData}) => {
console.log('Chat - received subscription: ', previousState, subscriptionData)
const newMessage = subscriptionData.data.Message.node
const messages = previousState.allMessages.concat([newMessage])
console.log('Chat - new messages: ', messages.length, messages) // prints the correct array with the new message!!
return {
allMessages: messages
}
},
onError: (err) => console.error(err),
})
}
After I sent the message through the chat, the subscription is triggered successfully and I see the two logging statements that also contain the expected data. So the contents of messages are definitely correct within updateQuery!
However, the UI doesn't update automatically, in fact, all the previously displayed messages disappear.
My render method looks as follows:
render() {
console.log('Chat - render: ', this.props.allMessagesQuery)
return (
<div className='Chat'>
<ChatMessages
messages={this.props.allMessagesQuery.allMessages || []}
/>
<ChatInput
message={this.state.message}
onTextInput={(message) => this.setState({message})}
onResetText={() => this.setState({message: ''})}
onSend={this._onSend}
/>
</div>
)
}
The logging statement in render shows that initially, this.props.allMessagesQuery has the array allMessages, so everything works after the initial loading.
After the subscription is received, allMessages disappears from this.props.allMessagesQuery which is why an empty array is given to ChatMessages and nothing is rendered.
Before subscription is triggered ✅
After subscription is triggered ❌
I figured it out after digging through the docs one more time, it was a mistake on my end!
This part from the Apollo docs helped me solve the issue:
Remember: You'll need to ensure that you select IDs in every query where you need the results to be normalized.
So, adding the id fields to the returned payload of my queries and subscriptions actually helped.
const allMessages = gql`
query allMessages {
allMessages {
id
text
createdAt
sentBy {
id
name
location {
id
latitude
longitude
}
}
}
}
`
subscription {
Message(filter: {
mutation_in: [CREATED]
}) {
node {
id
text
createdAt
sentBy {
id
name
}
}
}
}
In your update Query the previousState is the allMessages query that is saved in the react apollo store.
To update the store you have to make a deep copy or use some helper e.g. reacts immutability helper. The apollo developers page gives some information about how to update the store correctly update Query.
In your case it could look like this
...
updateQuery: (previousState, { subscriptionData }) => {
const newMessage = subscriptionData.data.Message.node;
return update(previousState, {
allMessages: { $push: [newMessage] },
});
}
...

Categories