RelayJS Invariant Violation with integer query parameter - reactjs

I'm relatively new to Relay, so this may be an easy mistake I've made but I've been looking for some time already and I haven't found any information about the problem I'm having.
This is the error that I get from my application:
Uncaught Error: Invariant Violation: GraphQLFragmentPointer: Value for the argument to story on query Route should be a string, but it was set to 10. Check that the value is a string.
The problem is I actually want it to be 10 and don't want it to be string. Have I configured something incorrectly?
This is my GraphQL Schema:
var queryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
node: nodeField,
story: {
type: storyType,
args: {
storyID: {
description: 'Story ID',
type: GraphQLInt
}
},
resolve: (root, {storyID}) => {
if (storyID) {
return Story.get(storyID)
} else {
return Story.get(10)
}
}
},
}),
});
This is the relay route I've defined:
export default class extends Relay.Route {
static queries = {
story: () => Relay.QL`
query {
story(storyID: $storyID)
}
`,
};
static paramDefinitions = {
storyID: {
required: false
},
};
static routeName = 'StoryRoute';
};
And this is how I instantiate it:
let route = new Route({storyID: 10})

Ok, it looks like I've figured it out finally.
It appears that root fields are severely limited and can currently only have no parameters, a single string parameter or a multiple string parameters, connected straight with IDs of objects fetched.
Look for more information here: https://github.com/facebook/relay/issues/112
and here: https://github.com/facebook/relay/issues/94

Related

Axios Adds Brackets [] To Request Param

I'm trying to add a list of items as a request parameter. When I manually write the URL like that: http://localhost:8080/api/user/book/?keyword=&page=1&pageSize=5&field=id&order=ascend&author=aziz&author=nazim, I get what I want.
However, Axios insists on adding square brackets pointlessly.
const _fetchBooks = async (params) => {
const url = `${UrlUtil.userURL()}/book/`;
const response = await axios.get(url, {
params: {
keyword: params.search,
page: params.pagination.current,
pageSize: params.pagination.pageSize,
field: params.sorter?.field,
order: params.sorter?.order,
author: params.filter?.author,
},
...
};
Here's the result URL React trying to reach when I do filtering:
http://localhost:8080/api/user/book/?keyword=&page=1&pageSize=5&field=id&order=ascend&author[]=aziz&author[]=nazim
With the brackets, I cannot go on because I get an exception:
java.lang.IllegalArgumentException: Invalid character found in the request target [/api/user/book/keyword=&page=1&pageSize=5&field=id&order=ascend&author[]=aziz&author[]=nazim ]. The valid characters are defined in RFC 7230 and RFC 3986
So, Java doesn't like the brackets.
As #AndyRay said in the comment, it's not React doing this, its Axios. This is standard behaviour as documented by their API. You can change the way that arrays get serialized using paramsSerializer option:
const response = await axios.get(url, {
params: {
keyword: params.search,
page: params.pagination.current,
pageSize: params.pagination.pageSize,
field: params.sorter?.field,
order: params.sorter?.order,
author: params.filter?.author,
},
paramsSerializer: { indexes: null }
});
as I can understand your issue of Invalid character found
I suggest you to read this first:
What's valid and what's not in a URI query?
To solve this issue of using invalid characters such as "[" you need to encode the url params and to do so check this fuction:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
Example:
const _fetchBooks = async (params) => {
const url = `${UrlUtil.userURL()}/book/`;
const response = await axios.get(url, {
params: {
keyword: params.search,
page: params.pagination.current,
pageSize: params.pagination.pageSize,
field: params.sorter?.field,
order: params.sorter?.order,
author: encodeURIComponent(JSON.stringify(params.filter?.author)),
},
...
};
Best of luck ...

How can I map data of multiple collections in snapshot?

I am not too confident working with Firestore and have trouble with more complex API calls to get data. Usually I use SQL backends in my apps.
For the section that I am working on, I would like to combine three collections to get an array of ToDos with the involved users and the category the current user labelled this ToDo with. Every involved person can label the ToDo like they prefer, which makes things a little more complicated. Broken down the collections are structured as follows.
todo: Firestore Database Document
{
title: string,
involved: string[], //user ids
involvedCategory: string[] //category ids mapped by index to involved
}
(I tried to have an array of objects here instead of the two arrays, but it seems I would not be able to query the array for the current user´s ID, like mentioned here, so this is a workaround)
category: Firestore Database Document
{
title: string,
color: string
}
user: Firebase Authentication User
{
uid: string,
displayName: string,
photoURL: string,
...
}
THE GOAL
An array of ToDo items like this:
{
id: string,
title: string,
involved: User[],
category?: {
title: string,
color: string
}
}
As I am working with TypeScript, I created an interface to use a converter with. My code looks like this so far:
import {
DocumentData,
FirestoreDataConverter,
WithFieldValue,
QueryDocumentSnapshot,
SnapshotOptions,
query,
collection,
where,
} from 'firebase/firestore'
import { store } from '../firebase'
import { useCollectionData } from 'react-firebase-hooks/firestore'
import { User } from 'firebase/auth'
import { useCategories } from './categories'
import { useAuth } from '../contexts/AuthContext'
interface ToDo {
id: string
title: string
involved: User[]
category?: {
title: string
color: string
}
}
const converter: FirestoreDataConverter<ToDo> = {
toFirestore(todo: WithFieldValue<ToDo>): DocumentData {
return {} //not implemented yet
},
fromFirestore(
snapshot: QueryDocumentSnapshot,
options: SnapshotOptions
): ToDo {
const data = snapshot.data(options)
return {
id: snapshot.id,
title: data.title,
category: undefined, //?
involved: [], //?
}
},
}
export function useToDos() {
const { currentUser } = useAuth()
const { categories } = useCategories() //needed in converter
const ref = query(
collection(store, 'habits'),
where('involved', 'array-contains', currentUser.uid)
).withConverter(converter)
const [data] = useCollectionData(ref)
return {
todos: data,
}
}
Is there any way I can do this? I have a Hook that returns all of the user´s categories, but I obviously can´t call that outside the
useToDos-Hook. And creating the const in the hook does not help, either, as it results in an infinite re-render.
I know this is a long one, but does anyone have tips how I could approach this? Thanks in advance ^^
UPDATE:
I had to make two small adjustments to #ErnestoC ´s solution in case anyone is doing something similar:
First, I changed the calls for currentUser.id to currentUser.uid.
Afterwards I got the very missleading Firestore Error: PERMISSION_DENIED: Missing or insufficient permissions, which made me experiment a lot with my security rules. But that is not where the error originated. Debugging the code line by line, I noticed the category objects resolved by the promise where not correct and had a weird path with multiple spaces at the beginning and the end of their ids. When I removed them before saving them in the promises array, it worked. Although I do not see where the spaces came from in the first place.
promises.push(
getDoc(
doc(
store,
'categories',
docSnap.data().involvedCategory[userCatIndex].replaceAll(' ', '')
)
)
)
The general approach, given that Firestore is a NoSQL database that does not support server-side JOINS, is to perform all the data combinations on the client side or in the backend with a Cloud Function.
For your scenario, one approach is to first query the ToDo documents by the array membership of the current user's ID in the involved array.
Afterwards, you fetch the corresponding category document the current user assigned to that ToDo (going by index mapping between the two arrays). Finally, you should be able to construct your ToDo objects with the data.
const toDoArray = [];
const promises = [];
//Querying the ToDo collection
const q = query(collection(firestoreDB, 'habits'), where('involved', 'array-contains', currentUser.id));
const querySnap = await getDocs(q);
querySnap.forEach((docSnap) => {
//Uses index mapping
const userCatIndex = docSnap.data().involved.indexOf(currentUser.id);
//For each matching ToDo, get the corresponding category from the categories collection
promises.push(getDoc(doc(firestoreDB, 'categories', docSnap.data().involvedCategory[userCatIndex])));
//Pushes object to ToDo class/interface
toDoArray.push(new ToDo(docSnap.id, docSnap.data().title, docSnap.data().involved))
});
//Resolves all promises of category documents, then adds the data to the existing ToDo objects.
await Promise.all(promises).then(categoryDocs => {
categoryDocs.forEach((userCategory, i) => {
toDoArray[i].category = userCategory.data();
});
});
console.log(toDoArray);
Using the FirestoreDataConverter interface would not be that different, as you would need to still perform an additional query for the category data, and then add the data to your custom objects. Let me know if this was helpful.

class-validator doesn't validate arrays

I can't get the class-validator to work. It seems like I am not using it: everything works as if I didn't use class-validator. When sending a request with an incorrectly formatted body, I don't have any validation error, although I should.
My DTO:
import { IsInt, Min, Max } from 'class-validator';
export class PatchForecastDTO {
#IsInt()
#Min(0)
#Max(9)
score1: number;
#IsInt()
#Min(0)
#Max(9)
score2: number;
gameId: string;
}
My controller:
#Patch('/:encid/forecasts/updateAll')
async updateForecast(
#Body() patchForecastDTO: PatchForecastDTO[],
#Param('encid') encid: string,
#Query('userId') userId: string
): Promise<ForecastDTO[]> {
return await this.instanceService.updateForecasts(userId, encid, patchForecastDTO);
}
My bootstrap:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(PORT);
Logger.log(`Application is running on http://localhost:${PORT}`, 'Bootstrap');
}
bootstrap();
I can't find what's wrong. What did I miss?
In the current version of NestJS (7.6.14), validating a request body that is a JSON array is supported using the built in ParseArrayPipe.
#Post()
createBulk(
#Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
See the official docs or the source code for more info.
NestJS actually does not support array validation out of the box. In order to validate an array, it must be wrapped in an object.
This way, I would not use a DTO corresponding to a list of items, but a DTO corresponding to an object that contains a list of items:
import { PatchForecastDTO } from './patch.forecast.dto';
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class PatchForecastsDTO {
#IsArray()
#ValidateNested() // perform validation on children too
#Type(() => PatchForecastDTO) // cast the payload to the correct DTO type
forecasts: PatchForecastDTO[];
}
And I would use that DTO in my controller:
#Patch('/:encid/forecasts/updateAll')
async updateForecast(
#Body() patchForecastsDTO: PatchForecastsDTO,
#Param('encid') encid: string,
#Query('userId') userId: string
): Promise<ForecastDTO[]> {
return await this.instanceService.updateForecasts(userId, encid, patchForecastsDTO);
}

React Relay fetch list

I need to fetch list
I have Schema
export const Schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
activities: {
name: 'Activities',
type: new GraphQLList(activityType),
resolve: (root, args, { rootValue }) => {
return User.findById(rootValue.req.user.id)
.populate('activities')
.then((user) => user.activities);
},
},
When I do graphql request using curl and sends
query ActivitiesQuery {
activities {
name
}
}
it gives me
{
"data": {
"activities": [
{
"name": "Eat"
}
]
}
}
Can somebody give me example how can I fetch it using relay ? because all the examples give object and then list like Store: { teas: [...
activities is an example of a plural non-identifying field. It is plural in the sense that it returns a list of things, and non-identifying in the sense that the individual elements of that list are not identified by global IDs.
Relay does not support plural non-identifying fields at the root, but support is coming. Follow along at https://github.com/facebook/relay/issues/112

Removing an object from an array inside a Collection

I'm using the following methods to push and pull objects into the "following" array, which is a property on each user's profile.
Meteor.users.update({ _id: this.userId }, { $push: {
"profile.following": { _id: _id, service: service, type: type }}
});
// I specify that it is required to match these two properties to remove an object
Meteor.users.update({ _id: this.userId }, { $pull: {
"profile.following": { service: service, type: type } }
});
This approach does work, it removes the object, but I always get this error:
Exception while simulating the effect of invoking 'unfollow' Error: documentMatches needs a document {stack: (...), message: "documentMatches needs a document"} Error: documentMatches needs a document
at Error (native)
at _.extend.documentMatches (http://localhost:3000/packages/minimongo.js?e8806aa7782b729b2517ebc0cd10b321667f1427:1341:13)
at MODIFIERS.$pull (http://localhost:3000/packages/minimongo.js?e8806aa7782b729b2517ebc0cd10b321667f1427:3414:24)
at http://localhost:3000/packages/minimongo.js?e8806aa7782b729b2517ebc0cd10b321667f1427:3124:9
at Function._.each._.forEach (http://localhost:3000/packages/underscore.js?0a80a8623e1b40b5df5a05582f288ddd586eaa18:164:22)
at http://localhost:3000/packages/minimongo.js?e8806aa7782b729b2517ebc0cd10b321667f1427:3105:9
at Function._.each._.forEach (http://localhost:3000/packages/underscore.js?0a80a8623e1b40b5df5a05582f288ddd586eaa18:164:22)
at Function.LocalCollection._modify (http://localhost:3000/packages/minimongo.js?e8806aa7782b729b2517ebc0cd10b321667f1427:3098:7)
at LocalCollection._modifyAndNotify (http://localhost:3000/packages/minimongo.js?e8806aa7782b729b2517ebc0cd10b321667f1427:848:19)
at http://localhost:3000/packages/minimongo.js?e8806aa7782b729b2517ebc0cd10b321667f1427:765:12
I'm not sure if this is a bug with Meteor or if I maybe I'm not writing the query correctly.
Apparent $elemMatch is needed to do this:
Meteor.users.update({ _id: this.userId }, { $pull: {
"profile.following": { $elemMatch: { service: service, type: type } } }
});

Resources