How to pass auth uid as a query parameter without getting undefined? - reactjs

I am using react-query-firebase library to make a query to a document.
According to documentation this is how you make a query:
const ref = doc(firestore, 'users', id);
const product = useFirestoreDocument(["users", id], ref);
Everything works when I manually set id like this:
const id = "pW5CizOJOpXezr5lGGsh";
However I want to set the uid dynamically and I am using auth state hook as well:
const user = useAuthUser(["user"], auth);
const ref = doc(firestore, "users", user.data.uid);
the problem is that on page load user initializes as undefined and I need to wait for a split second for user.data.uid to populate and because of that I am getting TypeError: Cannot read property 'indexOf' of undefined
is there a way to skip making a query until user has finished loading and only then make a query.

with react-query, you would usually just disable the query until you have all the dependencies via the enabled property:
useQuery(["users", id], ref, { enabled: Boolean(id) }
This is documented in the dependent queries section of the docs.

Related

How to clear & invalidate cache data using RTK Query?

I was facing a problem for sometime, that was I'm unable to clear cache using RTK query.
I tried in various ways but cache data is not clear.
I used invalidatesTag in my mutation query and it called the api instantly. But in this case I want to refetch multiple api again, but not from any rtk query or mutation. I want to make the api call after some user activity like click.
How can I solve this problem?
I made a separate function where I return api.util.invalidateTags(tag) or api.util.resetApiState().
this is my code-snipet:-
` const api = createApi({.....})
export const resetRtkCache = (tag?: String[]) => {
const api =
if (tag) {
return api.util.invalidateTags(tag)
} else {
return api.util.resetApiState()
}
}`
& I called it using dispatch method from other files
`const reloadData = () => {
dispatch(resetRtkCache())
}`
but here cache data is not removed.I think dispatch funtion is not working. I don't see the api call is being sent to server in the browser network.
But in this case I want to refetch multiple api again, but not from
any rtk query or mutation. I want to make the api call after some user
activity like click. How can I solve this problem?
So if I understood correctly what you want to achieve is to fetch some api that you have in RTK only after some kind of user interaction?
Can't you just define something like this?
const { data } = useGetYourQuery({ skip: skipUntilUserInteraction })
Where skipUntilUserInteraction is a component state variable that you will set to true and update to false based on the user interaction you need? (e.g. a click of a button).
So essentially on component render that specific endpoint will be skipped but will be fetched after the interaction that you want will happen?
wow, you actually asking so many questions at once. but I think you should definitely read the documentation because it covers all the questions you have.
so trying to answer your questions one by one.
I used invalidatesTag in my mutation query and it called the api instantly.
invalidating with Tags is one of the ways to clear the cache.
you should first set the tagTypes for your API then use those tags in mutation queries and tell the RTK query which part of entities you want to clear.
I want to refetch multiple APIs again
you can customize the query inside of a mutation or query like this example and by calling one function query you can send multiple requests at once and if you want to fetch the API again after the cache removed you do not need to do anything because RTK query will do it for you.
I want to make the API call after some user activity like click
every mutation gives u a function that you can pass to onClick like below:
import { use[Mymutation]Mutation } from 'features/api';
const MyComponenet() {
const [myMutationFunc, { isLoading, ...}] = use[Mymutation]Mutation();
return <button type='button' onClick={myMutationFunc}>Click for call mutaion</button>
}
and remember if you set providesTags for your endpoint which you were defined in tagTypes by clicking on the button and firing up the myMutationFunc you will be clearing the cache with those tags.
and if you looking for an optimistic update for the cache you can find your answer in here.
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}

React Firebase collection where and orderBy not working togehter

I am using React NextJS and Firebase with it. For my application, I have a collection query which goes by
const userParticipantsRef = db.collections('participants')
.where(
"users",
"array-contains",
username,
).orderBy('updated', 'asc')
const [snapshot, loading] = useCollection(userParticipantsRef);
// snapshot?.docs -> Undefiend [if I use orderBy]
// snapshot?.docs -> Array<> [if I don't use orderBy]
I have made sure that there is an updated field in the document
Still returns undefined
snapshot?.docs returns undefined
If I don't use order, then it returns the array perfectly

Why is currentUser.uid undefined

I'm trying to add information to the document of the user currently logged in.
I have the following code snippet of code in my component -
console.log("user", auth.currentUser?.uid);
useEffect(() => {
if (productId) {
db.collection("users")
.doc(auth.currentUser?.uid)
.collection("cart")
.doc(productId)
.onSnapshot((snapshot) => setProduct(snapshot.data()));
}
}, []);
Here,
const auth = firebase.auth();
The console log actually gives me the uid of the user but the hook below produces an errror -
FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: undefined
I have used the same approach in another component to add data and it works fine.
Why does this happen? Thanks in advance.
auth.currentUser is going to be null if there is no user signed in at the moment it was accessed. Your code is blindly ignoring this possibility by using the ? operator to "safely" access its properties anyway. When you use ?, if the prior expression is "falsy", the entire expression becomes undefined. Your code should instead check for null before assuming there is an object to use.
const currentUser = auth.currentUser
if (currentUser) {
const uid = currentUser.uid;
}
else {
// what do you want to do if there is no one signed in?
}
If you need to wait until a user is actually signed in, you should use an auth state observer to get a callback that tells you when the user object is available.
See also: Typescript the safe navigation operator ( ?. ) or (!.) and null property paths

useQuery Hook Data

I can return a query's data using useQuery no problem. I'm trying to retrive a user with the following query but when accessing data.user my app throws an error to say data is undefinded.
const userQuery = gql`
{
user {
id
email
}
} `;
export default NavWrapper = () => {
const { loading, error, data } = useQuery(userQuery);
console.log(data);
if (loading) return <ActivityIndicator size="large"/>
if (!data) return <Login/>;
return <Navigator/>;
}
The above works fine and retrives the user within an object, but I need to render the login comp based on the actual user rather than the data wrapper. Any ideas? must be simple...
Likely you have unsafe property access and are trying to use data.user before the query finishes. Essentially, your code is operating under the assumption data will always be present, whereas the nature of a query is such that there is a beginning unqueried state in which there is no data, followed by a loading state during which the query is being made and no data is available, followed by a somewhat final state in which data is made available. Your code should be prepared for all the states.
To account for these situations, you should use safe property access like so:
const user = data ? data.user : null
Alternatively, you can use a safe property access lib like idx:
const user = idx(data, _ => _.user) || null
Further, you'll want to make sure any components consuming your user data are prepared for the case in which the user is not available to them.
Note: data.user isn't referenced any where in the code you linked. It'll be helpful to know exactly where you're calling it (e.g. in body of NavWrapper vs Login or elsewhere).

Caching in React

In my react App I have a input element. The search query should be memoized, which means that if the user has previously searched for 'John' and the API has provided me valid results for that query, then next time when the user types 'Joh', there should be suggestion for the user with the previously memoized values(in this case 'John' would be suggested).
I am new to react and am trying caching for the first time.I read a few articles but couldn't implement the desired functionality.
You don't clarify which API you're using nor which stack; the solution would vary somewhat depending on if you are using XHR requests or something over GraphQL.
For an asynchronous XHR request to some backend API, I would do something like the example below.
Query the API for the search term
_queryUserXHR = (searchTxt) => {
jQuery.ajax({
type: "GET",
url: url,
data: searchTxt,
success: (data) => {
this.setState({previousQueries: this.state.previousQueries.concat([searchTxt])
}
});
}
You would run this function whenever you want to do the check against your API. If the API can find the search string you query, then insert that data into a local state array variable (previousQueries in my example).
You can either return the data to be inserted from the database if there are unknowns to your view (e.g database id). Above I just insert the searchTxt which is what we send in to the function based on what the user typed in the input-field. The choice is yours here.
Get suggestions for previously searched terms
I would start by adding an input field that runs a function on the onKeyPress event:
<input type="text" onKeyPress={this._getSuggestions} />
then the function would be something like:
_getSuggestions = (e) => {
let inputValue = e.target.value;
let {previousQueries} = this.state;
let results = [];
previousQueries.forEach((q) => {
if (q.toString().indexOf(inputValue)>-1) {
result.push(a);
}
}
this.setState({suggestions: results});
}
Then you can output this.state.suggestions somewhere and add behavior there. Perhaps some keyboard navigation or something. There are many different ways to implement how the results are displayed and how you would select one.
Note: I haven't tested the code above
I guess you have somewhere a function that queries the server, such as
const queryServer = function(queryString) {
/* access the server */
}
The trick would be to memorize this core function only, so that your UI thinks its actually accessing the server.
In javascript it is very easy to implement your own memorization decorator, but you could use existing ones. For example, lru-memoize looks popular on npm. You use it this way:
const memoize = require('lru-memoize')
const queryServer_memoized = memoize(100)(queryServer)
This code keeps in memory the last 100 request results. Next, in your code, you call queryServer_memoized instead of queryServer.
You can create a memoization function:
const memo = (callback) => {
// We will save the key-value pairs in the following variable. It will be our cache storage
const cache = new Map();
return (...args) => {
// The key will be used to identify the different arguments combination. Same arguments means same key
const key = JSON.stringify(args);
// If the cache storage has the key we are looking for, return the previously stored value
if (cache.has(key)) return cache.get(key);
// If the key is new, call the function (in this case fetch)
const value = callback(...args);
// And save the new key-value pair to the cache
cache.set(key, value);
return value;
};
};
const memoizedFetch = memo(fetch);
This memo function will act like a key-value cache. If the params (in our case the URL) of the function (fetch) are the same, the function will not be executed. Instead, the previous result will be returned.
So you can just use this memoized version memoizedFetch in your useEffect to make sure network request are not repeated for that particular petition.
For example you can do:
// Place this outside your react element
const memoizedFetchJson = memo((...args) => fetch(...args).then(res => res.json()));
useEffect(() => {
memoizedFetchJson(`https://pokeapi.co/api/v2/pokemon/${pokemon}/`)
.then(response => {
setPokemonData(response);
})
.catch(error => {
console.error(error);
});
}, [pokemon]);
Demo integrated in React

Resources