I am thinking to move to graphQL from a standard REST API architecture. Currently I have a data wrapper which preloads a dataset after initialization with user info, and other meta data.
Is there a way for apollo client to build a single query requested by multiple components and send it to graphQL server after the components are loaded instead of sending separate queries for each component and form a data layer that is necessary for application to operate ?
What are the possibilities eventually other options to do this and make it better with GraphQL then the current implementation.
Queries in Apollo client have an option fetchPolicy with default value cache-first which means Apollo will try to read from your local cache first before attempting a network fetch. If you have multiple components requesting data that has been fetched already then no network calls will happen. Just ensure to return the id field of the requested data in your query even if your component doesn't use it to ensure proper caching.
Let's assume you have the following graphql schema:
type Query {
question(questionId: Int!): Question
tag(tagId: Int!): Tag
}
type Question {
questionId: Int!
content: String!
}
type Tag {
tagId: Int!
tagName: String!
}
If you want to retrieve 2 objects in one request you can write the following qraphql request:
{
question(questionId: 1) {
questionId
content
}
tag(tagId:1){
tagId
tagName
}
}
As output, you will receive the following JSON:
{
"data": {
"question": {
"questionId": 1,
"content": "how to code in js?"
},
"tag": {
"tagId": 1,
"tagName": "js"
}
}
}
Related
I created a Json Server Database like this:
"Time":
[
{
"id":1,
"name":
[
{
"id":1,
"checkin":
[
{
"id":1,
"date":"123",
"time":"123"
},
{
"id":2,
"date":"123",
"time":"123"
}
]
},
{
"id":2,
"checkout":
[
{
"id":1,
"date":"123",
"time":"123"
}
]
}
]
}
]
I don't want to get the entire Database and go through it. I just want to tell the Database where exactly my Object is and have it returned.
How would I call the call for example the first Check-in Object?
I use the Angular HttpClient like this:
this.http.get(endpoint, JSON.stringify(time), this.httpOptions))
So I need the Exact Endpoint in a format like: endpoint/id/id or similar
I imagined it like this: endpoint/time/1/1
With output:
[
{
"id":1,
"date":"123",
"time":"123"
}
]
If this is not possible please tell me anyways.
PS: The question from this thread is essentially the same as mine. Also the JSON documentation doesn't real help either, it just says you need custom routes for multilayer JSON strings but not how to implement these routes.
I'm not sure if I understand correctly where you are returning the data from. If you meant json-server, just look at the documentation (here) and then you could use an endpoint like "/posts?Id=2"
However, if you mean your own API, which does not have an endpoint that returns one record, e.g. by its ID, the only convenient solution is to create a service that will map the result from the server and return the desired value.
You can do all this in one place, but for clearer code, I recommend dividing it into:
service that will download data from the server
service that will map the data on the basis of a given parameter
component that will display the data
Below is a working example on Stackblitz.
example
Note that in the app-component I pass the ID 32 to the method from the mapping service as the parameter. The mapping service then calls a method that sends the request for all the data.
The important thing is that all data is returned to the application, not just one record. If the API you are using does not provide such an endpoint, it is not possible to return only one record.
Apparently a request like I wanted to call is still not possible. The only way to come close is to fake it with custom Routes and flattening the JSON structure like in this old thread.
My partner and I are working on some app, and recently we've come out with a problem merging frontend user data to the backend database data. That also raises the issue of how to keep this data consistent.
He's developing a frontend app using React and TypeScript, and I'm developing a backend API using Flask.
Before I found out that it would be pretty compute-intensive to merge this data, he wanted to send me the whole data he has every 5 seconds.
The user data looks like this. A user has one root folder, and there can be many folders nested in a folder. Also, there can be collections of texts nested in a folder.
interface Article {
id: string;
text: string;
}
interface Collection {
id: string;
name: string;
articles: Array<Article>;
}
interface Folder {
id: string;
name: string;
children: Array<Folder | Collection>;
}
Example:
{
"id": 1,
"name": "name1",
"children": [{
"id": 2,
"name": "name2",
"children": [{
"id": 3,
"name": "name3",
"articles": [{
"id": 4,
"text": "text"
}]
}]
}]
}
My MySQL db model looks like this:
DB Model
My partner thinks I should decline using SQL DB. Instead, we need to use MongoDB, so he will send to me a user data and I just put it in root field of every user in DB.
He insists that Backend should know nothing about user data model. The main argument is that if he would want to add some field there won't be any need to change Backend code, because he would write DB migrations himself and I just put it in the DB.
So, my main questions are:
How to keep data consistent between an SPA and DB, so there won't be any loading screens for user?
Should we really switch to MongoDB?
We have pretty nested data. Is it better to just put this Frontend user data model into DB, or should we use some kind of patches to DB model instead?
My partner says that I violate the single-responsibility principle according to SOLID, because I parse his user data model to store it in the DB or get it from there. Is it really so, or it's OK to do so?
I have a GQL cursor based pagination and have implemented the frontend in react using the apollo client library. This is my current server response, and things work well when requesting the next or the previous page.
{
"data": {
"users": {
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "1",
"endCursor": "6"
},
"edges": [
{
"node": {
"id": "1"
}
},
{
"node": {
"id": "6"
}
}
]
}
}
}
The issue which I have is;
I have a page count dropdown, 20, 50 and 100, which determines the number of record to be show on a page. So, if i start with 50 records per page, and let's say record number 74 is on page 2, when i switch to 20 records per page, the apollo client does not request the server, instead loads the data from the cache. This is good as caching helps avoid additional request, but now I do not know which page record 74 belongs to.
Solution I am after;
I am after a solution/library that can handle pagination in react which also takes into account the fact that the server wont be contacted always and therefore should keep track of the page.
So, after some deep dive into apollo and graphql cursors, there isn't a way for react to "guess" the start cursor for a particular page, and therefore is not practically possible for pagination to place nice when the cache is in play.
For this reason, I had to disable cache for this specific query using the following
const { loading, error, data } = useQuery(GET_GREETING, {
variables: { ... },
fetchPolicy: "network-only"
});
This will make sure the query is always fetched from the network and is not cached.
I am trying to build an application using GraphQL and React Relay.
As part of this I have created a root query with the following specification:
query {
AllServices {
edges {
node {
id
}
}
}
}
So I have created a mutation - which works well - called CreateGithubService.
Here is a snippet from the Relay mutation:
getFatQuery() {
return Relay.QL`
fragment on CreateGithubServicePayload {
createdService
}
`
}
getConfigs() {
return [{
type: "REQUIRED_CHILDREN",
children: [
Relay.QL`
fragment on CreateGithubServicePayload {
createdService {
id
}
}
`
]
}]
}
The problem I have is that this is not causing the views which rely on the information to update.
My AllServices query is not refetched and I am unable to specify it in the Fat Query.
How can I setup my mutation to add the element to the all services query?
Thanks!
REQUIRED_CHILDREN will not update the local graph; it's strictly for fetching extra data that is only used in the success callback of the mutation update. In fact, it's not even documented outside of the source code, so I'm not sure how you decided to use it like this...
Although you haven't said so, I assume you want this new node (createdService) to be added to the AllServices connection? If so, you need to tell Relay that your mutation affects that connection:
At the moment, Relay basically assumes all mutations affect nodes, not arbitrary queries. To my knowledge, you won't be able to configure a mutation to update a non-node root query. Therefore, you should add a node at the root that acts as the parent of your AllServices connection (the convention for this is typically viewer). In other words, make it work like this: query { viewer { AllServices { ... } } }
Your mutation payload should always return everything that has been changed, not just the new data. That means you need a way to refetch the AllServices connection from the payload. Once you add the viewer node to your schema, you can return that node in the mutation payload and change your fat query to specify that the connection has changed, e.g.: fragment on CreateGithubServicePayload { viewer { AllServices } }
Given those two schema changes, you can then configure your mutation using FIELDS_CHANGE and specify the fieldIDs like { viewer: this.props.viewer.id }
I'm just starting to use flux (with redux for now) and am wondering how relationships are supposed to be handled.
For an example we can use Trello that has boards with columns that contains cards.
One approach would be to have one store/reducer for boards and have all the data in it there but that means some very fat stores since they would have to contain all the actions for columns and cards as well.
Another approach i've seen is separating nested resources into for example BoardStore, ColumnStore and CardStore and use their ids as reference.
Here's an example of where I am a bit confused: you could have an action creator called addCard that does a request to the server to create a card with all the data. If you are doing optimistic update, you would have created a card object in one of your store before but you can't know the id it will have until you get back the request.
So in short:
Firing addCard
addCard does a request, in the meantime you return an action of type ADD_CARD_TEMP
you get the request and return an action of type ADD_CARD where the store/reducer changes the id.
Is there a recommended way to deal with this case? Nested store/reducers look a bit silly to me but otherwise you end up with very complex stores so it looks like a compromise really.
Yes, using ids across multiple stores much like a relational database is the way to do it right.
In your example, let's say you want to optimistically put a new card in a particular column, and that a card can only be in one column (one column to many cards).
The cards in your CardStore might look like this:
_cards: {
'CARD_1': {
id: 'CARD_1',
columnID: 'COLUMN_3',
title: 'Go to sleep',
text: 'Be healthy and go to sleep on time.',
},
'CARD_2': {
id: 'CARD_2',
columnID: 'COLUMN_3',
title: 'Eat green vegetables',
text: 'They taste better with onions.',
},
}
Note that I can refer to a card by the id, and I can also retrieve the id within the object. This allows me to have methods like getCard(id) and also be able to retrieve the id of a particular card within the view layer. Thus I can have a method deleteCard(id) that is called in response to an action, because I know the id in the view.
Within the card store, you would have getCardsByColumn(columnID), which would be a simple map over the card objects, and this would produce an array of cards that you could use to render the contents of the column.
Regarding the mechanics of optimistic updates, and how the use of ids affects it:
You can use a client-side id that is established within the same closure that will handle the XHR response, and clear the client-side id when the response comes back as successful, or instead roll back on error. The closure allows you to hold on to the client-side id until the response comes back.
Many people will create a WebAPIUtils module that will contain all the methods related to the closure retaining the client-side id and the request/response. The action creator (or the store) can call this WebAPIUtils module to initiate the request.
So you have three actions:
initiate request
handle success
handle response
In response to the action that initiates the request, your store receives the client-side id and creates the record.
In response to success/error, your store again receives the client-side id and either modifies the record to be a confirmed record with a real id, or instead rolls back the record. You would also want to create a good UX around that error, like letting your user try again.
Example code:
// Within MyAppActions
cardAdded: function(columnID, title, text) {
var clientID = this.createUUID();
MyDispatcher.dispatch({
type: MyAppActions.types.CARD_ADDED,
id: clientID,
columnID: columnID,
title: title,
text: text,
});
WebAPIUtils.getRequestFunction(clientID, "http://example.com", {
columnID: columnID,
title: title,
text: text,
})();
},
// Within WebAPIUtils
getRequestFunction: function(clientID, uri, data) {
var xhrOptions = {
uri: uri,
data: data,
success: function(response) {
MyAppActions.requestSucceeded(clientID, response);
},
error: function(error) {
MyAppActions.requestErrored(clientID, error);
},
};
return function() {
post(xhrOptions);
};
},
// Within CardStore
switch (action.type) {
case MyAppActions.types.CARD_ADDED:
this._cards[action.id] = {
id: action.id,
title: action.title,
text: action.text,
columnID: action.columnID,
});
this._emitChange();
break;
case MyAppActions.types.REQUEST_SUCCEEDED:
var tempCard = this._cards[action.clientID];
this._cards[action.id] = {
id: action.id,
columnID: tempCard.columnID,
title: tempCard.title,
text: tempCard.text,
});
delete this._cards[action.clientID];
break;
case MyAppActions.types.REQUEST_ERRORED:
// ...
}
Please don't get too caught up on the details of the names and the specifics of this implementation (there are probably typos or other errors). This is just example code to explain the pattern.