React-query: invalidateQueries doesn't work - react-query

I'm trying to invalidate queries when I create new comment.
const { data: comments } = useQuery("getComments", () => getComments({ originalKind: "NOTICE", originalSeq: id }));
const createCommentMutation = useMutation(postComment, {
onSuccess: async () => {
const queryClient = new QueryClient();
await queryClient.invalidateQueries("getComments");
},
});
The comment is created successfully, but invalidateQueries dose not working.
There is no default options...
every time i create comment, the query will invalidated

If you create a new QueryClient, it will have a new QueryCache, which is not associated with the cached data of your query. That's not how it works, and that's also not what any of the official examples / docs are showing.
What you have to do is get access to the client with useQueryClient() - another hook exported from react-query. This will give you the singleton QueryClient that you have put into the QueryClientProvider:
import { useQueryClient } from '#tanstack/react-query'
const queryClient = useQueryClient()
const createCommentMutation = useMutation(postComment, {
onSuccess: async () => {
await queryClient.invalidateQueries("getComments");
},
});

Related

use Effect not working to bring up my product, using axios use params,

This code is not working for me i am trying to pull data from my mongodb
const ProductScreen = ({ match }) => {
const [product, setProduct] = useState({});
const { id } = useParams();
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(
`/api/product/${encodeURIComponent(id)}`
);
setProduct(data);
};
fetchProduct();
}, []);
};
pull data from server of mongo db
It is possible when the component first mounts, id is null and useParams() doesn't get it till the second render. So add an if statement in your useEffect to make sure the id is present. Also add id to the dependency array, so if the id changes, you will refetch the data for it. Otherwise, with an empty dependency array, the useEffect will only run on first mount.
const ProductScreen = ({ match }) => {
const [product, setProduct] = useState({});
const { id } = useParams();
useEffect(() => {
const fetchProduct = async () => {
const { data } = await axios.get(
`/api/product/${encodeURIComponent(id)}`
);
setProduct(data);
};
if (id) {
fetchProduct();
}
}, [id]);
};

Sequential execution / queue of endpoint of createApi

One of my endpoints should be called one by one, since the backend doesnt support multiple parallel requests. Ive tried two ways to do it:
Using onQueryStarted:
const sequenceMutex = new Mutex()
async onQueryStarted(id, { dispatch, queryFulfilled }) {
// wait until the sequenceMutex is available
await sequenceMutex.waitForUnlock()
const releaseSequence = await sequenceMutex.acquire()
await queryFulfilled // the endpoint already started here, cant manage the execution
releaseSequence()
}
Using a different baseQuery. But I dont see any way to use a different baseQuery for an especific endpoint:
const sequenceMutex = new Mutex()
export const sequentialBaseQueryWithReauth = async (
args,
api,
extraOptions
) => {
// wait until the sequenceMutex is available
await sequenceMutex.waitForUnlock()
const releaseSequence = await sequenceMutex.acquire()
const result = await baseQueryWithAuthentication(args, api, extraOptions)
releaseSequence()
return result
}
You could iterate on your answer and kick that yourEndpointSequentialBaseQuery.
const apiSlice = api.injectEndpoints({
endpoints: (builder) => ({
yourEndpoint: builder.query({
queryFn: (args, api, extraOptions, baseQuery) => {
await mutex.waitForUnlock()
const releaseSequence = await mutex.acquire()
try {
return baseQuery({
url: '/your-route',
method: 'POST',
body: args,
})
} finally {
releaseSequence()
}
},
}),
}),
})
Finally I created a generic seuqnetial baseQuery function to reuse it across the app.
First, create a baseQuery that accepts a mutex instance as argument:
export const sequentialBaseQueryFactory = (mutex: InstanceType<typeof Mutex>) => {
const sequentialBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
args,
api,
extraOptions,
) => {
await mutex.waitForUnlock()
const releaseSequence = await mutex.acquire()
const result = await yourBaseQuery(args, api, extraOptions) // use your createApi baseQuery
releaseSequence()
return result
}
return sequentialBaseQuery
}
Then, create the custom baseQuery for your endpoint using a mutex instance :
import { Mutex } from 'async-mutex'
// mutex for sequential calls of your endpoint
const yourEndpointSequenceMutex = new Mutex()
const yourEndpointSequentialBaseQuery = sequentialBaseQueryFactory(yourEndpointSequenceMutex)
Use the custom baseQuery in your endoint:
const apiSlice = api.injectEndpoints({
endpoints: (builder) => ({
yourEndpoint: builder.query({
queryFn: (args, api, extraOptions) => {
return yourEndpointSequentialBaseQuery({
url: '/your-route',
method: 'POST',
body: args,
}, api, extraOptions)
},
}),
}),
})

Custom queryFn reusing other endpoints

Using code from https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#performing-multiple-requests-with-a-single-query
Note that I added the endpoint getRandomUser:
import {
createApi,
fetchBaseQuery,
FetchBaseQueryError,
} from '#reduxjs/toolkit/query'
import { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
endpoints: (build) => ({
getRandomUser: builder.query<User, void>({
query: () => ({
url: `users/random`,
}),
}),
getRandomUserPosts: build.query<Post, void>({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random') // avoid repetition
if (randomResult.error) throw randomResult.error
const user = randomResult.data as User
const result = await fetchWithBQ(`user/${user.id}/posts`)
return result.data
? { data: result.data as Post }
: { error: result.error as FetchBaseQueryError }
},
}),
}),
})
Since in my example code I already have a getRandomUser endpoint defined, I would like to avoid repetition in getRandomUserPosts await fetchWithBQ('users/random') and directly call the endpoint getRandomUser.
I tried to access it with _queryApi.endpoints but it doesn't seem to be defined (I don't know if it can point to a key in the very same object endpoints) and even if it would I didn't know how to use it to replace the repetition.
How to approach these situations?

How to inject a stub function when using Hapi.js server.inject

I have a hapijs project which is using the hapi-mongodb plugin.
In the handler I am using the hapi-mongodb plugin to make db calls. See below
internals.getById = async (request, h) => {
try {
const db = request.mongo.db;
const ObjectId = request.mongo.ObjectID;
const query = {
_id: ObjectId(request.params.id)
};
const record = await db.collection(internals.collectionName).findOne(query);
//etc.....
I want to be able to test this using server.inject(), but I am not sure how to stub the request.mongo.db and the request.mongo.ObjectID
it('should return a 200 HTTP status code', async () => {
const server = new Hapi.Server();
server.route(Routes); //This comes from a required file
const options = {
method: 'GET',
url: `/testData/1`
};
//stub request.mongo.db and request.mongo.ObjectID
const response = await server.inject(options);
expect(response.statusCode).to.equal(200);
});
Any ideas?
I worked this out and realised that the mongo plugin decorates the server object which can be stubbed.

Connect Apollo with mongodb

I want to connect my Apollo server with my mongoDB. I know there are many examples out there, but I get stuck at the async part and did not found a solution or example for that (that's strange, am I completly wrong?)
I started with the example from next.js https://github.com/zeit/next.js/tree/master/examples/api-routes-apollo-server-and-client .
But the mongodb integration is missing.
My code
pages/api/graphql.js
import {ApolloServer} from 'apollo-server-micro';
import {schema} from '../../apollo/schema';
const apolloServer = new ApolloServer({schema});
export const config = {
api: {
bodyParser: false
}
};
export default apolloServer.createHandler({path: '/api/graphql'});
apollo/schema.js
import {makeExecutableSchema} from 'graphql-tools';
import {typeDefs} from './type-defs';
import {resolvers} from './resolvers';
export const schema = makeExecutableSchema({
typeDefs,
resolvers
});
apollo/resolvers.js
const Items = require('./connector').Items;
export const resolvers = {
Query: {
item: async (_parent, args) => {
const {id} = args;
const item = await Items.findOne(objectId(id));
return item;
},
...
}
}
apollo/connector.js
require('dotenv').config();
const MongoClient = require('mongodb').MongoClient;
const password = process.env.MONGO_PASSWORD;
const username = process.env.MONGO_USER;
const uri = `mongodb+srv://${username}:${password}#example.com`;
const client = await MongoClient.connect(uri);
const db = await client.db('databaseName')
const Items = db.collection('items')
module.exports = {Items}
So the problem is the await in connector.js. I have no idea how to call this in an async function or how to provide the MongoClient on an other way to the resolver. If I just remove the await, it returns – obviously – an pending promise and can't call the function .db('databaseName') on it.
Unfortunately, we're still a ways off from having top-level await.
You can delay running the rest of your code until the Promise resolves by putting it inside the then callback of the Promise.
async function getDb () {
const client = await MongoClient.connect(uri)
return client.db('databaseName')
}
getDb()
.then(db => {
const apollo = new ApolloServer({
schema,
context: { db },
})
apollo.listen()
})
.catch(e => {
// handle any errors
})
Alternatively, you can create your connection the first time you need it and just cache it:
let db
const apollo = new ApolloServer({
schema,
context: async () => {
if (!db) {
try {
const client = await MongoClient.connect(uri)
db = await client.db('databaseName')
catch (e) {
// handle any errors
}
}
return { db }
},
})
apollo.listen()