React-Query: useInfiniteQuery - react-query

So, I have looked through the docs and answers on here and I'm still needing some help:
index.tsx
const getInfiniteArticles = ({ pageParams = 0 }) => {
const res = await axios.get('/api/articles', { params: { page: pageParams } });
return res.data;
}
api/articles.ts
const getArticles = async (req: NextApiRequest, res: NextApiResponse) => {
try {
const { page } = req.query;
const pageNum = Number(page);
const data = await NewsService.getArticles(getRange(pageNum));
return res.status(200).json({
data,
previousPage: pageNum > 0 ? (pageNum - 1) : null,
nextPage: pageNum + 1,
});
} catch (err) {
res.json(err);
res.status(405).end();
}
};
export default getArticles;
index.tsx
const { data: articlePages, fetchNextPage } = useInfiniteQuery(
'infinite-articles',
getInfiniteArticles,
{
getNextPageParam: (lastPage, allGroups) => {
console.log('lastPage: ', lastPage);
console.log('allGroups: ', allGroups);
return lastPage.nextPage;
}
});
const handleLoadMore = () => {
fetchNextPage();
};
console after clicking next page:
lastPage: { data: Array(50), previousPage: null, nextPage: 1}
allGroups: [
{ data: Array(50), previousPage: null, nextPage: 1},
{ data: Array(50), previousPage: null, nextPage: 1},
]
Any help on why I'm getting the same groups is appreciated! :)

So, it turns out my structure wasn't correct
const {
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
...options,
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})
queryFn: (context: QueryFunctionContext) => Promise<TData>
The queryFn is supposed to be a synchronous function that returns a Promise
I was either passing an async function or I was returning the TData not a promise.
updated and working:
const getInfiniteArticles = ({ pageParam = 0 }) => axios.get('/api/articles', { params: { page: pageParam } });
const { data: articlePages, fetchNextPage } = useInfiniteQuery('articles', getInfiniteArticles, {
getNextPageParam: (lastPage, pages) => {
// the returned axios response
return lastPage.data.nextPage;
}
});
Reference Page

Related

How can I test Vue 3 component with Pinia and axios token

I have a component which calls a store called users:
Store:
import { defineStore } from 'pinia'
import axios from 'axios'
export const useUserApiStore = defineStore('userApiStore', {
state: () => ({
}),
actions: {
list(filter) {
const url = import.meta.env.VITE_URL_GLOBALTANK_BASE_API + import.meta.env.VITE_EP_USERS_LIST;
return new Promise((resolve, reject) => {
axios.get(url, {
params: {
filter: filter,
},
}).then(res => {
resolve(res)
}).catch(error => {
reject(error)
})
})
},
listPaginated(params) {
const url = import.meta.env.VITE_URL_GLOBALTANK_BASE_API + import.meta.env.VITE_EP_USERS_LIST_PAGINATE;
return new Promise((resolve, reject) => {
axios.get(url, {
params: params,
}).then(res => {
resolve(res)
}).catch(error => {
reject(error)
})
})
},
get(id) {
const url = import.meta.env.VITE_URL_GLOBALTANK_BASE_API + import.meta.env.VITE_EP_USERS_SHOW;
return new Promise((resolve, reject) => {
axios.get(url.replace(":id", id)).then(res => {
resolve(res)
}).catch(error => {
reject(error)
})
})
},
delete(id) {
const url = import.meta.env.VITE_URL_GLOBALTANK_BASE_API + import.meta.env.VITE_EP_USERS_DELETE;
return new Promise((resolve, reject) => {
axios.delete(url, { params: { id: id } }).then(res => {
resolve(res)
}).catch(error => {
reject(error)
})
})
},
/* TABS */
store(params) {
const url = import.meta.env.VITE_URL_GLOBALTANK_BASE_API + import.meta.env.VITE_EP_USERS_STORE;
return new Promise((resolve, reject) => {
axios.post(url, params).then(res => {
resolve(res)
}).catch(error => {
reject(error)
})
})
},
update(params) {
const url = import.meta.env.VITE_URL_GLOBALTANK_BASE_API + import.meta.env.VITE_EP_USERS_UPDATE;
return new Promise((resolve, reject) => {
axios.put(url, params).then(res => {
resolve(res)
}).catch(error => {
reject(error)
})
})
}
}
})
My component UsersView has the method onMounted that calls getData from store:
onMounted(() => {
getData();
});
const getData = (numPage = 0) => {
let params = {
paginate: model.paginate,
filter: model.filter,
page: model.current_page,
};
if (numPage > 0) {
params.page = numPage;
model.current_page = numPage;
}
userApiStore
.listPaginated(params)
.then((res) => {
model.data = res.data.data;
console.log("data", model.data);
})
.catch((err) => {
if (err.response.status === 401) console.log("UNAUTHORIZED");
notificationsStore.showToast(t("notifications.unknown_error"), "error");
});
};
And my UsersVew test:
it("should render users list", async () => {
const wrapper = mount(UsersView, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn, stubActions: false })]
}
})
await flushPromises()
const usersList = wrapper.findAllComponents('[data-test="users-list"]')
console.log(usersList)
})
The problem is that I have a previous login where I get the access_token and I put it globally in axios headers like this:
axios.defaults.headers.common['Authorization'] = Bearer ${res.data.access_token};
But I want to test my component but my component doesn't has the token globally when I throw the tests the list of users is empty
If anyone can help me I would be very grateful

I keep getting [Unhandled promise rejection: Error: Request failed with status code 404] while deleting a post from my API in React Native

I just started coding and I tried to delete a post through my mongoDB API. The code that I used is pretty basic, my API requests all go through a reducer which is set up pretty clear. But when I try to delete a post from my DB, I get the error ''[Unhandled promise rejection: Error: Request failed with status code 404]''. Did I do something wrong here or is this a mongoDB problem? I've included the necessary code.
This is my reducer request:
const deleteWorkout = (dispatch) => {
return async (_id) => {
await trackerApi.delete(`/workouts/${_id}`);
dispatch({ type: "delete_workout", payload: _id });
};
};
This is my complete reducer code:
import createWorkoutDataContext from "./createWorkoutDataContext";
import trackerApi from "../api/tracker";
const workoutReducer = (workouts, action) => {
switch (action.type) {
case "get_workouts":
return action.payload;
case "add_workout":
return [
...workouts,
{
title: action.payload.title,
exercises: action.payload.exercises,
},
];
case "edit_workout":
return workouts.map((Workout) => {
return Workout._id === action.payload.id ? action.payload : Workout;
});
case "delete_workout":
return workouts.filter((Workout) => Workout._id !== action.payload);
default:
return workouts;
}
};
const getWorkouts = (dispatch) => async () => {
const response = await trackerApi.get("/workouts");
dispatch({ type: "get_workouts", payload: response.data });
};
const addWorkout = (dispatch) => {
return async (title, exercises, callback) => {
await trackerApi.post("/workouts", { title, exercises });
if (callback) {
callback();
}
};
};
const editWorkout = (dispatch) => {
return async (title, exercises, _id, callback) => {
await trackerApi.put(`/workouts/${_id}`, { title, exercises });
dispatch({ type: "edit_workout", payload: { _id, title, exercises } });
if (callback) {
callback();
}
};
};
const deleteWorkout = (dispatch) => {
return async (_id) => {
await trackerApi.delete(`/workouts/${_id}`);
dispatch({ type: "delete_workout", payload: _id });
};
};
export const { Context, Provider } = createWorkoutDataContext(
workoutReducer,
{ addWorkout, getWorkouts, deleteWorkout },
[]
);
This is my code where I use the delete function:
const WorkoutListScreen = () => {
const { workouts, getWorkouts, deleteWorkout } = useContext(WorkoutContext);
return (
<View style={styles.container}>
<Text style={styles.pageTitle}>My Workouts</Text>
<NavigationEvents onWillFocus={getWorkouts} />
<FlatList
data={workouts}
keyExtractor={(item) => item._id}
renderItem={({ item }) => {
return (
<TouchableOpacity
onPress={() => navigate("WorkoutDetail", { _id: item._id })}
>
<View style={styles.row}>
<Text style={styles.title}>{item.title}</Text>
<TouchableOpacity onPress={() => deleteWorkout(item._id)}>
<Ionicons style={styles.deleteIcon} name="trash-outline" />
</TouchableOpacity>
</View>
</TouchableOpacity>
);
}}
/>
I simply included the deleteWorkout function in my TouchableOpacity, so I suppose that the problem lies within the reducer code?
here is the error I keep getting:
[Unhandled promise rejection: Error: Request failed with status code 404]
at node_modules\axios\lib\core\createError.js:16:14 in createError
at node_modules\axios\lib\core\settle.js:17:22 in settle
at node_modules\axios\lib\adapters\xhr.js:66:12 in onloadend
at node_modules\event-target-shim\dist\event-target-shim.js:818:20 in EventTarget.prototype.dispatchEvent
at node_modules\react-native\Libraries\Network\XMLHttpRequest.js:614:6 in setReadyState
at node_modules\react-native\Libraries\Network\XMLHttpRequest.js:396:6 in __didCompleteResponse
at node_modules\react-native\Libraries\vendor\emitter\_EventEmitter.js:135:10 in EventEmitter#emit
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:414:4 in __callFunction
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:113:6 in __guard$argument_0
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:365:10 in __guard
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:112:4 in callFunctionReturnFlushedQueue
This is what the objects in my databse look like (through Postman):
{
"userId": "615e06f36ce5e5f1a69c675e",
"title": "Defaults test",
"exercises": [
{
"exerciseTitle": "shadowboxing",
"exerciseProps": {
"time": 0,
"sets": 0,
"reps": 0
},
"_id": "6184c6fa685291fb44778df7"
},
{
"exerciseTitle": "bag workout",
"exerciseProps": {
"time": 4,
"sets": 0,
"reps": 12
},
"_id": "6184c6fa685291fb44778df8"
},
{
"exerciseTitle": "Exercise",
"exerciseProps": {
"time": 4,
"sets": 3,
"reps": 12
},
"_id": "6184c6fa685291fb44778df9"
}
],
"_id": "6184c6fa685291fb44778df6",
"__v": 0
}
I'm gratefull for your help!

delete item from apiCall need reload page to deleted from client

i use redux toolkit with react native and mongodb (mongoose)
i delete item and it successfully deleted from db
but not in client and need to reload page
todoSlice :
import {createSlice} from '#reduxjs/toolkit';
export const todoSlice = createSlice({
name: 'todos',
initialState: {
todos: [],
pending: null,
error: null,
},
reducers: {
deleteTodo: (state, action) => {
return state
},
},
});
export const {deleteTodo} = todoSlice.actions;
export default todoSlice.reducer;
apiCall:
import axios from 'axios';
import {deleteTodo} from './todoSlice';
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
dispatch(deleteTodo());
} catch (err) {
console.log(err);
}
};
main :
const {todo} = useSelector(state => state);
const dispatch = useDispatch();
const {todos} = todo;
useEffect(() => {
getTodos(dispatch);
}, []);
const handleDelete = id => {
deleteOneTodo(id, dispatch);
};
you have to implement deleteTodo inside your todoSlice in order to remove the deleted id from your local state,
...
export const todoSlice = createSlice({
name: 'todos',
initialState: {
todos: [],
pending: null,
error: null,
},
reducers: {
deleteTodo: (state, action) => {
return state.filter((todo)=>todo.id!==action.payload.id);
},
},
});
...
and of course you have to pass the payload with the id of the todo you want to remove
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
dispatch(deleteTodo({id:id}));
} catch (err) {
console.log(err);
}
};
if you still have doubts you can follow this tutorial: https://www.youtube.com/watch?v=fiesH6WU63I
i just call 'getTodos' inside 'deleteOneTodo'
and delete 'deleteTodo' from reducer
i hope its a good practice
export const deleteOneTodo = async (id, dispatch) => {
try {
await axios.delete(`http://10.0.2.2:5000/todos/${id}`);
// i add this line =>
getTodos(dispatch);
} catch (err) {
console.log(err);
}
};

Cloud Firestore function triggers and transactions - how to return a promise correctly

I have a Cloud Firestore function trigger "onCreate". Depending on the value of a given field, I would like it to either update some documents via a transaction and to copy the created document as a record in Algolia, or to execute a completely different transaction. There are therefore several conditions and I am not sure that I am returning promises correctly, as sometimes the function is not copying the record in Algolia when expected.
I paste a simplified version of the code in case someone can help.
exports.createArticle = functions.firestore.document('articles/{articleId}').onCreate(async (snap, context) => {
const newDocData = snap.data()
if(newDocData) {
const userCreatorId = newDocData.createdBy
const userDocRef = imported.db.collection('users').doc(userCreatorId)
if(newDocData.type === 1) {
newDocData.objectID = newDocData.id
indexAlgolia.saveObject(newDocData)
.then(() => {
console.log('Article saved in Algolia with id:', newDocData.objectID )
})
.catch(err => {
console.log('ERROR while ADDING object inAlgolia:', err)
})
return imported.db.runTransaction(async t => {
// do some work
const userDoc = await t.get(userDocRef)
const userData = userDoc.data()
if (userData && userData.field1 > 0) {
t.update(userDocRef, {field2: true})
}
}).then(result => {
console.log('Transaction success')
}).catch(err => {
console.log('Transaction failure:', err)
})
}
else {
const colOneRef = imported.db.collection('colOne')
colOneRef.where('field2', '==', newDocData.field3).limit(1).get().then(snapshot => {
return imported.db.runTransaction(async t => {
if (snapshot.empty) {
t.update(userDocRef, {field3: false})
}
const decrement = imported.fieldValue.increment(-1)
t.update(userDocRef, {field4: decrement})
}).then(result => {
console.log('Transaction success')
}).catch(err => {
console.log('Transaction failure:', err)
})
}).catch(() => 'Error while querying colOneRef')
}
}
})
When you have multiple async/then calls you canmake them await the result and run them as if they are synchornous but from your code I see that the second doesn't depent on the first one so you can put them in a Promse.all() to make the function finish faster because they will run in parallel. Your code would look like this:
xports.createArticle = functions.firestore
.document("articles/{articleId}")
.onCreate(async (snap, context) => {
const newDocData = snap.data();
if (newDocData) {
const userCreatorId = newDocData.createdBy;
const userDocRef = imported.db.collection("users").doc(userCreatorId);
if (newDocData.type === 1) {
newDocData.objectID = newDocData.id;
const firstPromise = indexAlgolia
.saveObject(newDocData)
.then(() => {
console.log(
"Article saved in Algolia with id:",
newDocData.objectID
);
})
.catch((err) => {
console.log("ERROR while ADDING object inAlgolia:", err);
});
const secondPromise = imported.db
.runTransaction(async (t) => {
// do some work
const userDoc = await t.get(userDocRef);
const userData = userDoc.data();
if (userData && userData.field1 > 0) {
t.update(userDocRef, { field2: true });
}
})
.then((result) => {
console.log("Transaction success");
})
.catch((err) => {
console.log("Transaction failure:", err);
});
return Promise.all([firstPromise, secondPromise]);
} else {
const colOneRef = imported.db.collection("colOne");
return colOneRef
.where("field2", "==", newDocData.field3)
.limit(1)
.get()
.then((snapshot) => {
return imported.db
.runTransaction(async (t) => {
if (snapshot.empty) {
t.update(userDocRef, { field3: false });
}
const decrement = imported.fieldValue.increment(-1);
t.update(userDocRef, { field4: decrement });
})
.then((result) => {
console.log("Transaction success");
})
.catch((err) => {
console.log("Transaction failure:", err);
});
})
.catch(() => "Error while querying colOneRef");
}
return
}
});

Mongoose update only the values that have changed

I have a PUT route to update value. I am hitting this route from two places. One is sending information about details and one about completed. The problem is that mongoose is updating booth even though it gets value from only one.
So if I send information about completed that it is true and latter I hit this route with new details (that dont have completed value) it will update completed also to false. How do I update just the value that was changed?
router.put('/:id', (req, res) => {
Todo.findOne({_id:req.body.id}, (err, foundObject) => {
foundObject.details = req.body.details
foundObject.completed = req.body.completed
foundObject.save((e, updatedTodo) => {
if(err) {
res.status(400).send(e)
} else {
res.send(updatedTodo)
}
})
})
})
EDIT:
Thanks to Jackson hint I was managed to do it like this.
router.put('/:id', (req, res) => {
Todo.findOne({_id:req.body.id}, (err, foundObject) => {
if(req.body.details !== undefined) {
foundObject.details = req.body.details
}
if(req.body.completed !== undefined) {
foundObject.completed = req.body.completed
}
foundObject.save((e, updatedTodo) => {
if(err) {
res.status(400).send(e)
} else {
res.send(updatedTodo)
}
})
})
})
const updateQuery = {};
if (req.body.details) {
updateQuery.details = req.body.details
}
if (req.body.completed) {
updateQuery.completed = req.body.completed
}
//or
Todo.findOneAndUpdate({id: req.body.id}, updateQuery, {new: true}, (err, res) => {
if (err) {
} else {
}
})
//or
Todo.findOneAndUpdate({id: req.body.id}, {$set: updateQuery}, {new: true}, (err, res) => {
if (err) {
} else {
}
})
Had a function similar to this my approach was this
const _ = require('lodash');
router.put('/update/:id',(req,res, next)=>{
todo.findById({
_id: req.params.id
}).then(user => {
const obj = {
new: true
}
user = _.extend(user, obj);
user.save((error, result) => {
if (error) {
console.log("Status not Changed")
} else {
res.redirect('/')
}
})
}).catch(error => {
res.status(500);
})
};
Taking new : true as the value you updating
It gets kinda ugly as the fields to be updated get increased. Say 100 fields.
I would suggest using the following approach:
try {
const schemaProperties = Object.keys(Todo.schema.paths)
const requestKeys = Object.keys(req.body)
const requestValues = Object.values(req.body)
const updateQuery = {}
// constructing dynamic query
for (let i = 0; i < requestKeys.length; i++) {
// Only update valid fields according to Todo Schema
if ( schemaProperties.includes(requestKeys[i]) ){
updateQuery[requestKeys[i]] = requestValues[i]
}
}
const updatedObject = await TOdo.updateOne(
{ _id:req.params.idd},
{ $set: updateQuery }
);
res.json(updatedObject)
} catch (error) {
res.status(400).send({ message: error });
}