Good way to query server object being in local cache - reactjs

So I have a database that store users. When someone log on my website, it stores in apollo cache a user as currentUser. And I only store his id.
So I made a query to get a user by passing his id :
query {
user(id: "id") {
id
username
avatar
}
}
But everytime I wanna get data for that user I need to make two query (the first one locally to get back his id from the cache and a second one to the server).
const GET_CURRENT_USER = gql`
query getCurrentUser {
currentUser #client
}
`;
const GET_USER_DATA = gql`
query getUser($id: String!) {
user(id: $id) {
id
username
avatar
}
}
`;
const currentUserData = useQuery(GET_CURRENT_USER);
const { currentUser } = currentUserData.data;
const { data, loading } = useQuery(GET_USER_DATA, {
variables: { id: currentUser.id },
fetchPolicy: "cache-and-network"
});
Is here a way that I can reduce that to only one query (the one to the server) ?

id value stored in the cache can be read using readQuery, you can store it in other global store/state, f.e. redux.
If you're using apollo cache as global store then using queries is a natural part of this process.
Using readQuery you can read the value without querying (but doing the same). One query 'saved' ;)
Deeper integration (additional query, local resolver) is not a good thing - creating unnecessary dependencies.
If you want to reuse this "unneccessary query" extract it to some module or create a custom hook (id read/used/saved once during initialization) - probably the best solution for this scenario.
Another solutions:
make login process providing user data - for some inspiration take a look at apollo-universal-starter-kit - but this is for initial data only (login/avatar changing during session??) - further user querying still needs an id parameter - it must be stored and read somewhere in the app.
make id optional parameter (for getUser query - if you can change backend) - if not provided then return data for current user (id read from session/token)

Related

Merge query results of queryOneEntityById and queryAllEntitiesByIds in Apollo by __typename

There are two queries to get Posts. One is to get specific post by id (returns object)
query PostById ($id : ID!) {
post (id: $id) {
title
description
}
}
another one is to get all posts by ids (returns array of objects)
query PostsByIds ($ids : ID!) {
posts (ids: $ids) {
title
description
}
}
Entities in both of them have same __typename though from what I see Apollo caches them separately.
Is there a way to tell Apollo to treat everything with same __typename as a part of one data pool? So for example if we already have Post with id===1 received with PostsByIds([1]) on the call to PostById(1) apollo leverage cached data and not making API call?

Subscribe to firestore from React.useEffect using two queries (async operation)

I have three collections in a firestore database: users, customers and nodes. And example of the properties of interest of their documents is:
User (Document name: IDUSER)
{
...<Non interesting properties>,
customer: ref = customers/IDCUSTOMER
}
Customer (Document name: IDCUSTOMER)
{
...<Non interesting properties>
}
Node (Document name: IDNODE)
{
...<Non interesting properties>,
customer: ref = customers/IDCUSTOMER
}
With this configuration, I want to group users by their assigned customer (one customer can have multiple users) and use this customer assigned to provide access to the nodes assigned to the given customer. Thus, all users from a customer can view the customer's nodes. I don't have problems with this configuration.
My problem is that in a ReactJS application (+Typescript), I want to subscribe a given component to the nodes collection in order to get new added nodes. For this, I need to:
Generate a query (q1) to get the customer assigned to the current user.
Get data from q1 (an async function).
Generate a query (q2) to get the customer's nodes (this query depends on the data received from step 2).
Execute the onSnapshot function, and return the unsubscribe function to the React.useEffect hook in order to execute it when the component is dismounted.
My problem is that the flow depends on an async function, the final unsubscribe function is a Promise<Unsubscribe> function and it throws error.
I know that to use async in React.useEffect is a bad idea.
How can I implement this???
Thank you very much in advance for your time.

Firestore function to listen for updates

I am a beginner in React native and firestore, and using these to build a kind of social media app, and I have a weird problem(I think I structured the db the wrong way). I want to have a feed, with all posts, no following-based, no nothing. The first time I structured my posts in db like this: users(collection)->user(doc)->thisUserPosts(collection inside doc) - but I couldn't find a way to fetch through all the thisUserPosts from all user(doc) and display them properly.
So I re-structured the db like this:
2 main collection, posts and users. Completely different. In users collection, only docs of users and their data(name, age, etc). In the other, their posts(name, media, desc, AND userId - where userId == the person who created it. userId field from posts collection docs should exist in users collection).
This second approach works just fine. In feed, I only fetch posts. But the problem arrises when I try to open the post(need to have this feature). I need to be able to display on react-navigation header the name of the user, yet I only have details of the post and only userId, which is to no good use.
So I came up with a solution : add a userName field in the posts collection doc, next to userId and simply display that. Now here's the catch: I need to figure a way(in firestore I think) to listen to updates from users collection docs, in case a user updates his name/username(I don't want to showcase the old name). And I don't know if that's possible inside firestore or how. Or is it better to find a different db structure?
TLDR: Need a function in firestore to listen to updates from other collection OR restructuring the db.
If you are fetching posts of a single user then you can just set a listener for his document.
Make sure that document has no sensitive information that must not be shared with others and is limited to the owner only.
If you are fetching posts from multiple users then you can use in operator:
db.collection("users").where("userID", "in", ["user_id1", "user_id2"])
.onSnapshot((snapshot) => {
console.log(snapshot.docs.map(user => user.data()))
});
If I assume you will be updating the new name in all the user's posts then you can set the listener on the posts document itself but that won't be nice in case all 30 posts fetched are from same user. That'll end up costing 30 reads just to update the same name.
Edit:
A simple example of reading a user's posts and listening updates on the user name:
const userID = "my_user_id"
// fetching user's 30 posts
const postsRef = firebase.firebase().collection("posts").where("userID", "==", userID).limit(30)
const postsSnapshot = await postsRef.get()
const postsData = postsSnapshot.docs.map(post => post.data())
// Array of posts data objects
// listening to change in user's name
firebase.firestore().collection("users").doc("user_id")
.onSnapshot((doc) => {
console.log("data: ", doc.data());
const newUsername = doc.data().username
const updatedPostsData = postsData.map(post => {
return ({...post, username: newUsername})
})
});

Handle progressive disclosure on frontend with Apollo Client

In my project, we plan to implement a progressive disclosure (displaying some part of UI data if the user has permissions). Basically, we are using react + apollo client with hooks + graphqltag.
But the problem is no that how to hide some part of UI but how to split queries by permissions. So right now for pages, we create a single query containing many "subqueries" for different kinds of data. For example :
export const GET_DATA_X= gql`
query getDataX(
$applicationId: ID!
$dateFrom: String!
$dateTo: String!
$displayMode: String!
) {
applicationShipDates(
applicationId: $applicationId
dateFrom: $dateFrom
dateTo: $dateTo
displayMode: $displayMode
) {
periodStartDate
dates
}
graphStatistics(
applicationId: $applicationId
dateFrom: $dateFrom
dateTo: $dateTo
) {
totalVisits
totalConversions
conversionRate
}
}
`;
And right now each part of this query will be available if the user will have permission. On the backend side, it's already handled. We throw null/empty array and error. But IMO we shouldn't even ask for this part of the data. And that is the question. Do you have any suggestions on how to do this with an apollo client?
Right now I have two ideas on how to do that:
Split queries into single and make a few API calls if the user has permission, otherwise skip it
Write a custom function where I will pass as a prop array of objects, including query definition and query required permissions. I will filter this array by permission and from small query definitions like applicationShipDates or graphStatistics i will create a big query like getDataX which will includes few "subqueries"
Like #xadm mentioned directives will be the best solution.

How to delete data from firebase live database using id?

I am trying to delete a record in firebase live database using this id.
My removeFromFavorites action takes in a recipe (with the id) and I want to match that with the record in the database and delete it.
export const removeFromFavorites = (recipe: RecipeConfig) => async (
dispatch: Dispatch
): Promise<void> => {
};
If this is not possible, can you please suggest an alternative, thanks.
To delete (or otherwise write to) a node from the database you must know its full path. If you don't know it's full path, you can determine that by first running a query, and then deleting the node/all nodes that match the query.
If recipe has an id, then that'd be something like this:
const ref = firebase.database().ref('favorites');
const query = ref.orderById().equalTo(recipe.id);
query.once('value').then((results) => {
results.forEach((snapshot) => {
snapshot.ref.remove();
});
});
Also see:
How to delete specific record in firebase having specified Title
Firebase - Delete a node
Firebase query and delete data in javascript

Resources