unable to update cache after mutation - javascript

I have a screen where I run a query and render some items based on the results:
query getMyFavoritePlaces($myID: String) {
favorite_place(where: { user_id: { _eq: $myID } }) {
id
location
label
user{
id
}
user_id
}
}
const { data, error } = useGetMyFavoritePlacesQuery({ variables: { myID: profile.id }});
{
data && (
<FlatList
data={data.favorite_place}
renderItem={({ item }) => <FavoriteLocationEntry place={item} />}
keyExtractor={(item) => item.id.toString()} />
)
}
When I run this mutation by clicking on any of the items from the flatlets:
mutation RemoveFavoriteLocation($id: Int!) {
delete_favorite_place_by_pk(id: $id) {
id
location
label
user{
favorite_places {
id
location
label
}
}
user_id
}
}
The object gets deleted successfully and the UI is also updated. However, I get a warning that
Cache data may be lost when replacing the favourite_place field of a Query object.
To address this problem define a custom merge function for the Query.favorite_place field, so InMemoryCache can safely merge these objects: { ... }
The values shown here are the ids. I am already returning ids in both, the query and mutation. How can I fix it then?
It might not be possible to run the favourite_place query itself within the mutation.

Your mutation needs to include an id field for the user or it may mistakenly delete a favourite_place field for a different user:
mutation RemoveFavoriteLocation($id: Int!) {
delete_favorite_place_by_pk(id: $id) {
id
location
label
user{
**id**
favorite_places {
id
location
label
}
}
user_id
}
}
P.s please don't include the asterisks, just there to bring focus to the missing property.

Related

Filter firestore request by separate steps

I am making a filter so that a user can search for exactly a few parameters, without having to put them all in the form. For example you could search for games of type X but of all genres (Leave the form empty and it is an empty array).
I had tried doing the whole query at the same time but since there are empty arrays an exception is thrown. Consequently, I have thought of going step by step checking the filter parameters and discarding the empty ones, but I don't know how to save each step in the reference.
Technologies:
Ionic
Angular
Firebase - Firestore
Service Code:
getFilteredResultsGames(filter: Filter) {
return this.firestore.collection<Game[]>(environment.db_tables.games,
(ref) => {
if (filter.types.length > 0){
ref.where('id_type', 'in', filter.types)
}
if (filter.genders.length > 0) {
ref.where('ids_genders', 'array-contains-any', filter.genders)
}
return ref
}).valueChanges({ idField: 'id' });
}
Model Code:
export interface Filter {
genders: string[];
types: string[];
}
Firestore queries objects are immutable. Calling where on a ref or query, doesn't modify the existing object, but rather returns a new query with the additional condition.
So to return the new query, you'd do something like:
getFilteredResultsGames(filter: Filter) {
return this.firestore.collection<Game[]>(environment.db_tables.games,
(ref) => {
if (filter.types.length > 0){
ref = ref.where('id_type', 'in', filter.types)
}
if (filter.genders.length > 0) {
ref = ref.where('ids_genders', 'array-contains-any', filter.genders)
}
return ref
}).valueChanges({ idField: 'id' });

Prisma constraint to ensure single item in database

I have a Category model inside my prisma schema that has a field called parent:
model Category {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
title String #unique
parent Boolean
parentId Int?
stores Storefront[]
products Product[]
}
How can I ensure there is always at least 1 Category where the parent is true? And if not, send back an error. Do I have to do this manually inside a mutation?
Example, updateCategory mutation:
export default async function updateCategory({ where, data }: UpdateCategoryInput, ctx: Ctx) {
const { title, parent, parentId } = CategoryInput.parse(data)
const category = await db.category.update({
where,
data: {
title,
parent,
parentId: parent ? null : parentId,
},
})
return category
}
It seems this is business logic you would have to implement in your resolver before update, I don't think this can be achieved by setting constraints in the DB or on a Prisma-level.
Here's what I would suggest to enforce this constraint in your application:
export default async function updateCategory({ where, data }: UpdateCategoryInput, ctx: Ctx) {
const { id, title, parent, parentId } = CategoryInput.parse(data)
// Check how many categories there are with `parent` set to `true`
const categories = await db.category.findMany({
where: { parent: true }
})
if (categories.length === 1 && categories[0].id === id && !parent) {
// if all these conditions are true, you are about to set the last
// category with `parent` equals `true` to `false` which must not happen
throw new Error(`This mutation would set the last parent to false`)
}
const category = await db.category.update({
where: {
id: id
},
data: {
title,
parent,
parentId: parent ? null : parentId,
},
})
return category
}
Note that you would have to add this check to every part in your application where parent is potentially set to false.
Let me know if that helps or if you have any further questions :)

Vuejs return data from component to Laravel

I am trying to make a search bar to collect a list of products which the user will then be able to select an array of products and add it to an order.
So far the products can be searched, added to a "products" array via an "add" button and they are able to see the data within products. They are also able to remove products that they do not want.
My issue is that I am trying to send the data from the "products" array with a parent form. The search component is located within the form as well as the list. But I do not know how I would send the array with the form.
I do not need to send the entire product object, but just the ID which will be used to link up the products with the order.
Here is where the component is:
<div class="uk-margin" id="search">
<label for="Search" class="uk-form-label">Search products to add</label>
<search input="ess"></search>
</div>
Here is the vuejs component data:
export default {
data() {
return {
keywords: null,
results: [],
products: []
};
},
watch: {
keywords (after, before) {
this.fetch();
}
},
methods: {
fetch() {
axios.get('/search', { params: { keywords: this.keywords } })
.then(response => this.results = response.data)
.catch(error => {});
},
addProduct (product) {
let duplicate = false;
this.products.forEach((item, index) => {
if (item.ID === product.ID) {
duplicate = true;
}
});
if (!duplicate) {
this.products.push(product);
}
},
removeProduct (product) {
Vue.delete(this.products, product);
}
}
}
Everything works fine, but my question is.. How am I able to pass the data back to the html / laravel to use it while sending data to the controller. I only need to send the "products" array, I have tried using input with the data but it isn't working. Does anyone know how I could do it, or is the best way to do so, using JavaScript and add them to an array by finding all the elements which are being displayed?
Many thanks.

VueJS components prop execution order

I'm using vuejs and vue-apollo in my project.
I have to components : GroupList and GroupForm
The thing is that I first arrive on the list, and then when I click on one of the groups, I arrive in the form view of that group.
To do that, I have a button that opens the form view by giving the id of that group :
openGroupFormView (group, index) {
this.$router.push({ name: 'GroupFormView', params: { groupId: group.id } })
}
In the past, I used to pass the group object as paramater and it worked but I changed my mind and now I want to pass the id so that when the component GroupForm is opened, the graphql query to retrieve this group based on the id is executed. I want to do that in order to avoid apollo-cache inconsistencies in case where someone edited the same group in the meantime.
So in my GroupForm component I have :
My prop :
props: {
groupId: Number
}
My component query :
apollo: {
group: {
query: GROUP_QUERY,
variables () {
return {
id: this.groupId
}
},
..
}
The associated query beneath that :
const GROUP_QUERY = gql`
query ($id: ID) {
group (id: $id) {
id
name
usersCount
userIds {
id
username
}
}
}
`
My problem is that in data () of my component, I used to use the group that I passed through the props as an object. Now, I still want to do things on the group but the apollo part is executed after the data () part so I have an undefined problem. How could I ensure that this problem doesn't occur ? I mean that apollo query is run first.
My data() block :
data () {
return {
form: {
name: this.group === undefined ? '' : this.group.name
}
}
},
Thanks in advance for your help !
EDIT : A part of my template
<b-form>
<b-form-group label="Name">
<b-form-input
type="text"
v-model="form.name"
required
:disabled="!editing"/>
</b-form-group>
<div v-if="editing" class="mb-3">
<b-button #click="cancelEdit()">Cancel</b-button>
<b-button #click="group === undefined ? createGroup() : updateGroup(group)" variant="success">Save</b-button>
</div>
</b-form>
I don't use Apollo but it looks like you should be able to initialize name to an empty string and then use the result hook to set it when the query completes.
Untested:
apollo: {
group: {
...
result({ data, loading, networkStatus }) {
this.form.name = data.name // something like this
}
}
}

Query a specific GET field?

How can I get just a single field (called "name") from an entry in my AppSync/Dynamo table, for a given "id:"? This is instead of getting the entire row of fields for that 'id' returned to me. I only need the single field.
The following doesn't work:
const userName = await API.graphql(graphqlOperation(GetUsers, { id: "3b342de-34k5-434....." }, "name") );
Schema:
type Users #model {
id: ID!
name: String
email: String
}
Query:
export const getUsers = `query GetUsers($id: ID!) {
getUsers(id: $id) {
id
name
email
}
}
Try below
export const getUser = `query GetUsers($id: ID!) {
getUsers(id: $id) {name}
}`
const userName = (await API.graphql(graphqlOperation(getUsers, {id: "xxx"}))).getUser.data.name
Be aware that the await gives you a nested object that includes the name. You have to extract it out. Besides, what you really need is type User, not type Users.
Your query is querying getUsers from your graphql server, and is asking for id, name, and email.
If you only want the name, change the query to this:
query GetUsers($id: ID!) {
getUsers(id: $id) {
name
}
}

Categories

Resources