How to filter GraphQL query with relational parameters, React Native - react-native

I have a React Native App that gives me a list of properties. Each property has it's own page and a link to a screen that lists all the notes that are associated with that property AND that user. So, a user can only see the notes that they have created for this specific property on this screen.
I'm having trouble writing a GraphQL query that gets all notes associated with the current property AND the current user.
My basic instinct is to write it like this...
const getNotes = gql`
query getNotes {
allPropertyNotes(filter: {
AND: [{
propertyId: ${this.props.navigation.state.params.id}
}, {
userId: ${this.props.screenProps.user.id}
}]
}, orderBy: createdAt_DESC) {
id
title
body
}
}
`;
But this doesn't seem to work because the query is defined outside of the component and doesn't have access to this.props (I'm assuming).
My next though was to do it like this
const getNotes = gql`
query getNotes($userId: ID!, $propertyId: ID!) {
allPropertyNotes(filter: {
AND: [{
propertyId: $propertyId
}, {
userId: $userId
}]
}, orderBy: createdAt_DESC) {
id
title
body
}
}
`;
But I'm not sure how to bind the variables to the ID's that I need them to be. Any pointers on how to do this?

If you need only 'query on mount' then just render <Query/> component using props as variables. Example with variables
The same you can do with graphql() options.variables
Passing query (fired at start by default) and [many] mutations as props are for more complex cases. docs
Docs are not verbose here, you have to gather knowledge from many places/pieces or search for tutorials.

Related

Vuejs - update array of an object which is in an array

I'm developing a helpdesk tool in which I have a kanban view.
I previously used nested serializers in my backend and I managed to have everything working with a single query but it's not scalable (and it was ugly) so I switched to another schema :
I query my helpdesk team ('test' in the screenshot)
I query the stages of that team ('new', 'in progress')
I query tickets for each stage in stages
So when I mount my component, I do the following :
async mounted () {
if (this.helpdeskTeamId) {
await this.getTeam(this.helpdeskTeamId)
if (this.team) {
await this.getTeamStages(this.helpdeskTeamId)
if (this.stages) {
for (let stage of this.stages) {
await this.getStageTickets(stage)
}
}
}
}
},
where getTeam, getTeamStages and getStageTickets are :
async getTeam (teamId) {
this.team = await HelpdeskTeamService.getTeam(teamId)
},
async getTeamStages (teamId) {
this.stages = await HelpdeskTeamService.getTeamStages(teamId)
for (let stage of this.stages) {
this.$set(stage, 'tickets', [])
}
},
async getStageTickets (stage) {
const tickets = await HelpdeskTeamService.getTeamStageTickets(this.helpdeskTeamId, stage.id)
// tried many things here below but nothing worked.
// stage.tickets = stage.tickets.splice(0, 0, tickets)
// Even if I try to only put one :
// this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0])
// I see it in the data but It doesn't appear in the view...
// Even replacing the whole stage with its tickets :
// stage.tickets = tickets
// this.stages.splice(this.stages.indexOf(stage), 1, stage)
},
In getTeamStages I add an attribute 'tickets' to every stage to an empty list. The problem is when I query all the tickets for every stage. I know how to insert a single object in an array with splice or how to delete one object from an array but I don't know how to assign a whole array to an attribute of an object that is in an array while triggering the Vue reactivity. Here I'd like to put all the tickets (which is a list), to stage.tickets.
Is it possible to achieve this ?
If not, what is the correct design to achieve something similar ?
Thanks in advance !
EDIT:
It turns out that there was an error generated by the template part. I didn't think it was the root cause since a part of the view was rendered. I thought that it would have prevent the whole view from being rendered if it was the case. But finally, in my template I had a part doing stage.tickets.length which was working when using a single query to populate my view. When making my API more granular and querying tickets independently from stages, there is a moment when stage has no tickets attribute until I set it manually with this.$set(stage, 'tickets', []). Because of that, the template stops rendering and raises an issue. But the ways of updating my stage.tickets would have worked without that template issue.
I could update the stages reactively. Here is my full code; I used the push method of an array object and it works:
<template>
<div>
<li v-for="item in stages" :key="item.stageId">
{{ item }}
</li>
</div>
</template>
<script>
export default {
data() {
return {
stages: [],
};
},
methods: {
async getTeamStages() {
this.stages = [{ stageId: 1 }, { stageId: 2 }];
for (let stage of this.stages) {
this.$set(stage, "tickets", []);
}
for (let stage of this.stages) {
await this.getStageTickets(stage);
}
},
async getStageTickets(stage) {
const tickets = ["a", "b", "c"];
for (let ticket of tickets) {
this.stages[this.stages.indexOf(stage)].tickets.push(ticket);
}
},
},
mounted() {
this.getTeamStages();
},
};
</script>
It should be noted that I used the concat method of an array object and also works:
this.stages[this.stages.indexOf(stage)].tickets = this.stages[this.stages.indexOf(stage)].tickets.concat(tickets);
I tried your approaches some of them work correctly:
NOT WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, tickets)
WORKED
this.$set(this.stages[this.stages.indexOf(stage)].tickets, 0, tickets[0]);
WORKED
stage.tickets = tickets
this.stages.splice(this.stages.indexOf(stage), 1, stage)
I'm sure it is XY problem..
A possible solution would be to watch the selected team and load the values from there. You seem to be loading everything from the mounted() hook, and I suspect this won't actually load all the content on demand as you'd expect.
I managed to make it work here without needing to resort to $set magic, just the pure old traditional vue magic. Vue will notice the properties of new objects and automatically make then reactive, so if you assign to them later, everything will respond accordingly.
My setup was something like this (showing just the relevant parts) -- typing from memory here, beware of typos:
data(){
teams: [],
teamId: null,
team: null
},
watch:{
teamId(v){
this.refreshTeam(v)
}
},
methods: {
async refreshTeam(id){
let team = await fetchTeam(id)
if(!team) return
//here, vue will auomaticlly make this.team.stages reactive
this.team = {stages:[], ...team}
let stages = await fetchStages(team.id)
if(!stages) return
//since this.team.stages is reactive, vue will update reactivelly
//turning the {tickets} property of each stage reactive also
this.team.stages = stages.map(v => ({tickets:[], ...v}))
for(let stage of this.team.stages){
let tickets = await fetchTickets(stage.id)
if(!tickets) continue
//since tickets is reactive, vue will update it accordingly
stage.tickets = tickets
}
}
},
async mounted(){
this.teams = fetchTeams()
}
Notice that my 'fetchXXX' methods would just return the data retrieved from the server, without trying to actually set the component data
Edit: typos

Use i18n with data fetched from Wordpress RestAPI

I'm stuck since a while on this problem now and I'm struggling to find answers or peoples that have experienced the same problem.
Also, I'm not a native English speaker and I'm new to programming, so sorry if it's not clear / if my approch is dumb as f
I decided to turn the Wordpress of my company to headless (on Nuxt). Everything was pretty fine until I tried to internationalized the app. The best solution (I think) to manage this task is to use nuxt-i18n, a tool to properly translate the app (strings, paths etc). But this solution seems to not be very compatible with data fetched from the Rest API.
For now I'm trying to passing the data from single page like that :
<script>
import SwitchL from '~/components/LanguageInput.vue'
export default {
components: {
SwitchL,
},
data() {
return {
fetchRestFr: null,
fetchRestEn: null,
i18nData: null,
}
},
i18n: {
messages: {
// help
}
},
methods: {
fetchSomeData() {
// Get page data in French
let fetchRestFr = this.$axios.$get(`https://www.company-site.com/wp-json/wp/v2/pages?include=42&lang=fr`);
// Get page data in English
let fetchRestEn = this.$axios.$get(`https://www.company-site.com/wp-json/wp/v2/pages?include=42&lang=en`);
// Assign data to null variables
this.fetchRestFr = fetchRestFr[0]
this.fetchRestEn = fetchRestEn[0]
// Build the i18n object
this.dataToI18n();
},
dataToI18n() {
if (this.fetchRestFr && this.fetchRestEn) {
let fr = this.fetchRestFr
let en = this.fetchRestEn
let data = {
messages: {
fr,
en
}
}
this.i18nData = data
}
},
},
created() {
this.fetchSomeData();
},
}
</script>
An other approch was to use the tag outside the template section like so :
<i18n>
// Inject the data generated from a function
{
"en": {
"data": "here"
},
"fr": {
"data": "ici"
}
}
</i18n>
But I don't find any solution to dynamically inject JSON here.
Last solution is to make things to preper way, by creating JSON file for i18n to referencing, but I think It will be pretty hard for me to do this, and to manage this on long term.
If you have advice, thoughts on this I will be very grateful !
You do usually use some JSON files directly. They will be stored into /locales/fr.json with something like this
{
"here": "ici"
}
Then, you'll access it into your template with
<template>
<p>{{ $t('here') }}</p>
</template>
And it will handle the fr/en switch by the several ways available (manual switch, checking browser's locale, URL's path, cookie etc).
You can check nuxt-i18n's documentation for this and get a proper simple setup quickly here: https://i18n.nuxtjs.org/
Usually, you won't need to use the i18n tag anymore but if you still need to, you can do as explained here: https://i18n.nuxtjs.org/per-component-translations

Is it possible to query document schema metadata in Sanity/GROQ?

I have a simple singleton document schema defined in my Sanity/NextJS project, to model my "Colophon" page (richText is a custom block field type):
export default {
title: 'Colophon',
name: 'colophon',
type: 'document',
__experimental_actions: ['update', 'publish'],
fields: [
{
title: 'Body',
name: 'body',
type: 'richText',
validation: Rule => Rule.required(),
},
],
};
I retrieve this document with a simple query in my NextJS application:
export async function getStaticProps() {
const colophon = await client.fetch(`
*[_type == "colophon"][0]
`);
// ...
};
Is it possible to write a GROQ query to retrieve the meta title defined in the schema, i.e. Colophon? Although this is a singleton document, I would like to avoid repeating this string in my project if possible. At the moment, I can only see the fields on the document in my results, i.e. body.
Thanks for reading!
No, I don't believe there is.
As far as I understand what you're after; The schema is defined in the studio-instance and not the datastore. Those two are not hard coupled. I have several studio-instances with small variations on the schemas using one single project/datastore. The API you query to get data does not care which studio and schema was used and cant answer for the actual schema details.

Is there an easier way of updating nested arrays in react-native (react-redux)?

I am trying to update an array inside of my array (nested array) with react-redux. I found a solution to how to do this but is there any easier way of doing this rather than passing multiple parameter to the action.
[types.HISTORY_UPDATE](state, action){
return {
...state,
budgets: [
...state.budgets.slice(0,action.id),
{
key: action.key,
id: action.idd,
name: action.name,
budgetType: action.budgetType,
startDate: action.startDate,
currency: action.currency,
amount: action.amount,
amountLeft: action.amountLeft,
rollOver: action.rollOver,
color: action.color,
iconName: action.iconName,
history: [
...state.budgets[action.id].history.slice(0,action.histId),
{
note: action.note,
amount: action.amount,
type: action.type,
date: action.date,
fullDate: action.fullDate,
hours: action.hours,
min: action.min,
month: action.month,
year: action.year
},
...state.budgets[action.id].history.slice(action.histId+1)
]
},
...state.budgets.slice(action.id+1)
]
}
},
and the action goes like this
export function updateHistory(id,key,idd,name,budgetType,startDate,currency,amount,amountLeft,rollOver,color,iconName,histId,........){
I don't want to spend time with passing multiple parameter like this while using react-redux and also while I tried to run my application on my phone sometimes it really slows the application. Is it because of the example above?
I would be really appreciated If you guys come up with a solution.
I typically do not store arrays in redux, since updating a single element really is a burden as you noticed. If the objects you have inside your array all have a unique id, you can easily convert that array to an object of objects. As key for each object you take that id.
const convertToObject = (array) => {
let items = {};
array.map((item) => {
items[item.id] = item;
});
return items;
};
In your action you simply just pass the item you want to update as payload, and you can update the redux store very easily. In this example below I am just updating the budgets object, but the same logic applies when you assign a history object to each budget.
[types.BUDGET_UPDATE](state, action){
const item = action.payload;
return {
...state,
budgets: {
...state.budgets,
[item.id]: item
}
}
}
And if you want an array somewhere in your component code, you just convert the redux store object back to an array:
const array = Object.values(someReduxStoreObject);

How to grab apollo client cache content and use it in a query as an input type?

I am developing a react native application with apollo client and I need to have 2 screens:
Screen A: displays a list of profiles
Screen B: displays some filters which have to be applied to Screen A
So my idea was to use apollo client cache to save the state of the filters in screen B, and then come back to screen A and somehow refetch with the new filters applied.
Since there are a couple of filters that I need to send on each request to the server, I also was thinking about using an input type so I can send my filters in the form of an object instead of a list of comma-separated parameters.
So looking at the docs from apollo client, there is a section to manage the local stage. In there, I found a sub-section called Using #client fields as variables. Basically, this part tells you how to grab whatever filters were stored in the cache and send it as part of the query.
However, I always get the following error:
Invariant Violation: Missing selection set for an object of type ProfileParameters returned for query field profileParameters
some code:
This is how I initialized the cache-store, for now, it only contains page and pageSize but it will contain more parameters.
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
profile: (_, {id}, {getCacheKey}) =>
getCacheKey({__typename: 'Profile', id: id}),
},
},
});
cache.writeData({
data: {
profileParameters: {
__typename: 'ProfileParameters',
page: 0,
pageSize: 25,
},
},
});
const client = new ApolloClient({
uri: 'http://192.168.1.102:3000/graphql',
cache: cache,
resolvers: {},
});
And this is the query component and the query:
const PROFILES_QUERY = gql`
query getFilteredProfiles($type: String, $parameters: ProfileParameters) {
profileParameters #client #export(as: "parameters")
profiles(type: $type, parameters: $parameters) {
id
name
termOfEntry
sport
sportPosition
gpa
avatarUrl
imageUrl
nationality
countryOfResidence
dateOfBirth
annualBudget
career
satScore
toeflScore
graduationDate
}
}
`;
<Query
query={PROFILES_QUERY}
variables={{
type: this.PROFILE_TYPE,
}}>
...
</Query>
There is a type variable that is being passed in the variables object. That comes from a local variable in the class.
Also, screen B does not exist yet, but the cache is being initialized and I want to read whatever is in there so I can send those filters to the server.