{"id":25169713,"url":"https://github.com/sofiane-abou-abderrahim/react-events-tanstack-query","last_synced_at":"2026-05-07T04:41:16.981Z","repository":{"id":241799715,"uuid":"807587215","full_name":"sofiane-abou-abderrahim/react-events-tanstack-query","owner":"sofiane-abou-abderrahim","description":"Explore \"React Events Tanstack Query\", a demo app built with ReactJS, React Router, and Tanstack Query. 🚀 This project showcases efficient data fetching and mutations with advanced concepts like cache invalidation and optimistic updates. Features include event viewing, searching, adding, editing, and deleting.","archived":false,"fork":false,"pushed_at":"2024-06-05T01:19:46.000Z","size":1313,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-16T02:33:36.829Z","etag":null,"topics":["query-client","query-client-provider","react","react-query","react-router","react-router-dom","reactjs","tanstack-query","tanstack-react-query","usequery"],"latest_commit_sha":null,"homepage":"https://react-events-tanstack-query.onrender.com/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sofiane-abou-abderrahim.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-05-29T11:42:54.000Z","updated_at":"2024-06-23T13:23:31.000Z","dependencies_parsed_at":"2024-05-30T05:17:24.214Z","dependency_job_id":"41d62464-72f6-4efc-8f77-b00959a9738f","html_url":"https://github.com/sofiane-abou-abderrahim/react-events-tanstack-query","commit_stats":null,"previous_names":["sofiane-abou-abderrahim/react-events-tanstack-query"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sofiane-abou-abderrahim/react-events-tanstack-query","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-events-tanstack-query","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-events-tanstack-query/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-events-tanstack-query/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-events-tanstack-query/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sofiane-abou-abderrahim","download_url":"https://codeload.github.com/sofiane-abou-abderrahim/react-events-tanstack-query/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-events-tanstack-query/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274165975,"owners_count":25233952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-08T02:00:09.813Z","response_time":121,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["query-client","query-client-provider","react","react-query","react-router","react-router-dom","reactjs","tanstack-query","tanstack-react-query","usequery"],"created_at":"2025-02-09T08:36:18.192Z","updated_at":"2026-05-07T04:41:16.937Z","avatar_url":"https://github.com/sofiane-abou-abderrahim.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Data Fetching With Tanstack Query (formerly React Query)\n\n## Sending HTTP Requests With Ease\n\n- What Is Tanstack Query?\n\n  1. A library that helps with sending HTTP requests\n  2. \u0026 helps with keeping your frontend UI in sync with your backend data\n\n- Why Would You Use It?\n\n  1. You don't need Tanstack Query, but\n  2. it can simplify your code (and your life as a developer)\n     1. it is able to get rid of a bunch of code like state management or some other code as well\n     2. it gives you some advanced features, like caching, behind the scenes data fetching, etc\n\n- Fetching \u0026 Mutating Data\n- Configuring Tanstack Query\n- Advanced Concepts: Cache Invalidation, Optimistic Updating \u0026 More\n\n# React + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.\n\nCurrently, two official plugins are available:\n\n- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh\n- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh\n\n# Steps\n\n## 0. Project Setup\n\n2. in your terminal, run `cd backend` \u0026 `npm install` \u0026 `npm start`\n1. open a new terminal \u0026 run `npm install` \u0026 `npm run dev`\n\n## 1. Installing \u0026 Using Tanstack Query - And Seeing Why It's Great!\n\n1. in the terminal, run `npm install @tanstack/react-query`\n2. in `src\\components\\Events\\NewEventsSection.jsx`, use Tanstack Query:\n   1. cut the `fetchEvents()` function inside the `useEffect()` \u0026 paste it in a new file in `src/util/http.js`\n   2. export this function so that you can use it outside of that file with Tanstack Query\n   3. go back to `NewEventsSection.jsx` and get rid of the state management code \u0026 the useEffect() code\n   4. import the `{useQuery}` hook from `@tanstack/react-query`\n   5. use this `useQuery` hook inside the `NewEventsSection` component to send a HTTP request behind the scenes, etc\n   6. you must configure it by adding inside of it an object with:\n      1. first step:\n         1. a `queryFn` property which is a function that defines the actual code that will send the actual request because:\n            - Tanstack Query doesn't send HTTP requests, at least not on its own\n            - you have to write the code that sends the actual HTTP request\n            - Tanstack Query then manages the data, errors, caching \u0026 much more!\n         2. import the outsourced `fetchEvents()` \u0026 point at it as a value of the `queryFn` property\n      2. second step:\n         1. a `queryKey` property\n         2. as a value to it, set an array of values that are internally stored by React Query so that it can reuse existing data\n   7. you get an object back from `useQuery` from where you can pull out the elements you need, like:\n      1. the `data` property which holds the actual response data as a value, so the data that is returned by `fetchEvents()`\n      2. the `isPending` property\n      3. the `isError` property\n      4. the `error` property\n      5. \u0026 more\n   8. use now `isPending` instead of the old `isLoading` state to show the `LoadingIndicator` whilst you're waiting for a response\n   9. check for `isError` instead of the old `error` used with the state management to show the `ErrorBlock`\n   10. and in that `ErrorBlock` instead of just hardcoding the `message`, use the `error` object \u0026 its `info` \u0026 its `message`\n   11. output the `data` if we did successfully fetch the events\n   12. in `App.js`, wrap the components that use React Query with `QueryClientProvider` \u0026 `QueryClient` imported from `@tanstack/react-query`\n\n## 2. Understanding \u0026 Configuring Query Behaviors - Cache \u0026 Stale Data\n\n1. you can control the behaviour of React Query, for example by setting a `staleTime` on your queries\n   - this controls after which time React Query will send the behind the scenes request to get updated data if it found data in your cache\n   - the default value is `0`, which means it will use `data` from the cache, but always send this behind the scenes request to get updated data\n   - if you set it to `5000`, it will wait for 5 seconds before sending another request\n2. you can also set the `gcTime` property\n   - this controls how long the `data` in the cache will be kept around\n   - the default value is 5 minutes\n\n## 3. Dynamic Query Functions \u0026 Query Keys\n\n1. in `src\\components\\Events\\FindEventSection.jsx` import `{useQuery}` from `@tanstack/react-query`\n2. executes `useQuery()`\n3. in `http.js`, tweak a little bit the `fetchEvents()` function by adding a query parameter to the request\n4. go back to `FindEventSection.jsx` and configure the `useQuery` object \u0026 manage some state to update the query when the `searchTerm` changes\n5. get back the object from `useQuery()` \u0026 in there get back the `data`, `isPending`, `isError` \u0026 `error`\n6. use this pieces of information to dynamically \u0026 conditionally output the `content` in this component\n\n## 4. The Query Configuration Object \u0026 Aborting Requests\n\n1. React Query \u0026 the `useQuery()` hook passes some default data to the query function when assigned to `queryFn`\n   1. in `http.js`, `console.log(searchTerm)` to see that data\n   2. this is an object that gives information about:\n      - the `queryKey`\n      - a `signal` needed for aborting the request if you navigate away from the page before the request was finished\n2. therefore, in `http.js`, we should accept in `fetchEvents()` such an object and pull out for example\n   1. the `signal` then pass this `signal` to the `fetch()` function as a second argument\n   2. `searchTerm`\n3. now, in `FindEventSection.jsx`, make sure you:\n   1. pass an object to `fetchEvents()` \u0026 set a property named `searchTerm`\n   2. forward that `signal` from `http.js` via a `signal` object to the anonymous function \u0026 set it as an argument of `fetchEvents()`\n\n## 5. Enabled \u0026 Disabled Queries\n\n1. in `FindEventSection.jsx`, disable the query until a search term has been entered with help of the `enabled` property\n   1. initially, show no events\n   2. but, if a user searched something then clear the input, show all the events\n2. the `searchTerm` set should be initially undefined by not passing any value at all to `useState()`\n3. set `searchTerm !== undefined` as a value of `enabled`\n   1. so if if `searchTerm` is `undefined`, which is the initial value, the query will be disabled\n   2. but, if it's anything else, including `''` (which would be the case if the user cleared the input field manually), the query will be enabled\n4. use `isLoading` instead of `isPending` to get rid of the initial loading spinner which displayed because when a query is disabled, React treats it as `isPending`\n\n## 6. Changing Data with Mutations\n\n1. in `NewEvent.jsx`, use `useQuery()`:\n   1. to send data\n   2. to collect that data\n2. the data is already collected in `EventForm.jsx`\n3. to send the data, in `NewEvent.jsx`, use the `useMutation` hook which:\n   1. is optimized for such data changing queries\n   2. for example, by making sure that those requests are not sent instantly when the component renders unlike `useQuery`\n   3. but, that instead requests are only sent when you want to send them, for example, from inside the `handleSubmit` function\n4. use `useMutation()` \u0026 configure its object:\n   1. set a `mutationFn` function\n   2. for that, add a new `createNewEvent` function in `http.js`\n   3. set this function as a value for the `mutationFn` property\n5. `useMutation` returns an object that you can destructure to pull out some useful properties, like the:\n   1. `mutate` property which is a function that you can call anywhere in this component to send this request\n   2. `isPending` property\n   3. `isError` property\n   4. `error` property\n6. therefore, inside the `handleSumbit()` function, call `mutate()` \u0026 pass the `formData` to it as a value\n7. handle errors \u0026 show some loading text whilst the request is on its way with help of the returned `useMutation` object properties\n\n## 7. Fetching More Data \u0026 Testing the Mutation\n\n1. in `NewEvent.jsx`, display a list of images in the `\u003cEventForm\u003e` component\n2. so, in `EventForm.jsx`, you need to fetch that list of images from the backend\n3. to do that, use `useQuery()` \u0026 configure it\n4. in `http.js`, add a new `fetchSelectableImages` function\n5. in `EventForm.jsx`, set this `fetchSelectableImages` function as a value for the `queryFn` property\n6. set a value of `['events-images']` for the `queryKey` property\n7. use that object returned by `useQuery()` to get hold `data`, `isPending` \u0026 `isError`\n8. set the `data` (so, that list of images) as a value for the `images` prop on the `\u003cImagePicker\u003e` component\n9. show conditionally the list of images if we have `data`\n10. show a loading text if `isPending` is true\n11. show an the `\u003cErrorBlock\u003e` component if `isError` is true\n\n## 8. Acting on Mutation Success \u0026 Invalidating Queries\n\n1. in `NewEvent.jsx`, when creating a new event, wait for this mutation to be finished until navigating away\n   1. to do that, add a new `onSuccess` property to the `useQuery()` configuration object\n   2. it wants a function as a value that will be executed once this `mutationFn` succeeded\n   3. inside of this method, call `navigate('/events')`\n2. when creating a new event, the new event must be rendered straight away in the UI without switching to a different page then coming back to refetch data behind the scenes\n   1. React Query should immediately refetch data \u0026 update your data in the UI\n   2. in `NewEventsSection.jsx`, the `data` in the query should be marked as stale \u0026 refetch is triggered\n   3. you can achieve this by calling a method provided by React Query that allows us to invalidate one or more queries\n   4. before that, in `App.jsx`, with help of the `QueryClient` object, you will force this invalidation of a query\n   5. therefore, cut `QueryClient` \u0026 add it in `http.js` so that you can import it from multiple files\n   6. now, in `App.jsx`, import this `queryClient` constant from `http.js`\n   7. now, in `NewEvent.jsx`, in the `onSuccess` method before navigating away, call `queryClient.invalidateQueries()`\n   8. to target specific queries, `invalidateQueries()` takes an object as an input where you have to define the `queryKey` which you want to target\n   9. set `queryKey: ['events']` to invalidate all queries that include the `events` key even if it is not exactly the same key (if you don't use `exact: true`)\n\n## 9. A Challenge! The Problem\n\n1. update the `util/http.js` file so that you add to it the `fetchEvent()` \u0026 `deleteEvent()` functions\n2. use the `fetchEvent()` function together with React Query's `useQuery()` hook to fetch the event details in `EventDetails.jsx` \u0026 output the event details, like the event title, the image\n   1. in order to fetch the data for a single event, you'll need the ID of that event\n   2. you can get that in the `EventDetails` component via React Router's `useParams` hook\n3. make the delete button work by using the `deleteEvent()` function together with React Query's `useMutation()` in `EventDetails.jsx` so that you get a mutation which you can execute when this button is clicked\n\n## 10. A Challenge! The Solution\n\n1. use React Query's `useQuery()` hook to fetch data:\n   1. in `EventDetails.jsx`, import `useQuery` \u0026 execute it in the `EventDetails` component function\n   2. configure the query by adding:\n      1. `queryFn` which has `fetchEvent()` as a value \u0026 get access to the `id` with `useParams()` \u0026 pass it to the function\n      2. `queryKey` which has `['events', params.id]` as a value\n   3. get the object from `useQuery()` \u0026 pull out from it `data`, `isPending`, `isError` \u0026 `error` properties\n   4. use this pieces of data to output different content on the screen depending on the current state of this query\n   5. formate the `date` in a nice way\n2. use ReactQuery's `useMutation()` hook to delete data:\n   1. in `EventDetails.jsx`, import `useMutation()` \u0026 execute it in the `EventDetails` component function\n   2. configure the query by adding `mutationFn` \u0026 setting `deleteEvent` to it as a value\n   3. get the object from `useMutation()` \u0026 pull out from it the `mutate` property\n   4. trigger the `mutate()` fonction when the `delete` button is pressed by callig it inside a `handleDelete()` function\n      1. pass an object to `mutate()`\n      2. add an `id` property which has the id of that to be deleted event (`params.id`) as a value\n   5. connect the `handleDelete()` function to this `delete` button with help of the `onClick` prop\n   6. define what should happen after the mutation succeeds\n      1. add the `onSuccess` property to the mutation configuration object\n      2. set to it an anonymous function \u0026 inside of it navigate away with the React Router DOM's `useNavigate()` hook\n      3. invalidate your event related queries because\n         1. the data should be marked as outdated after deletion of the event with `queryClient` \u0026 `invalidateQueries`\n         2. and React Query should be forced to fetch data again\n\n## 11. Disabling Automatic Refetching After Invalidations\n\n1. in `EventDetails.jsx`, since we invalidate all queries, React Query immediately triggers a refetch for this details query \u0026 provokes an error in the console\n2. to avoid this behaviour, you must:\n   1. add a second `refetchType` property to this configuration object for `queryClient.invalidateQueries()`\n   2. and set its value to `'none'`\n   3. which makes sure that when you call `invalidateQueries()`, these existing queries (`queryKey: ['events]`) will not automatically be triggered again immediately\n   4. instead they will just be invalidated \u0026 the next time they will be required, they will run again\n\n## 12. Enhancing the Demo App \u0026 Repeating Mutation Concepts\n\n1. in `EventDetails.jsx`, add a confirmation modal before we trigger this deletion mutation\n   1. manage some `isDeleting` state with the `useState()` hook initially set to `false` that tells us whether the user started the deletion process or not\n   2. change this to `true` once the user clicks this `Delete` button\n   3. open up a modal where the user has to click another button to start this deletion mutation\n   4. add a new `handleStartDelete()` function in which you set `setIsDeleting` to `true`\n   5. add a new `handleStopDelete()` function in which you set `setIsDeleting` to `true` (if the user cancels this delete process)\n   6. connect `handleStartDelete` to the `Delete` button in this UI instead of `handleDelete`\n   7. but, now this UI should also contain another component that can be displayed\n      1. in your `return` statement, add the `\u003cModal\u003e` component\n      2. inside this component, show some confirmation text and `Cancel` \u0026 `Delete` buttons\n      3. this `Delete` button, when is clicked, should trigger the `handleDelete` function you used before to delete the event\n      4. this `Cancel` button should trigger the `handleStopDelete` function to stop the deletion mutation \u0026 close this modal again\n      5. because this modal should be display conditionally if `isDeleting` is `true`\n      6. this `\u003cModal\u003e` component takes an `onClose` prop which triggers `handleStopDelete` when this modal is closed\n2. having to wait for a short while before the event is being deleted is not ideal because you should give the user some feedback that this deletion was initiated\n   1. in the `useMutation` object, pull out:\n      1. `isPending` property \u0026 set it to `isPendingDeletion` to avoid a name clash\n      2. `isError` \u0026 name it `isErrorDeleting`\n      3. `error` \u0026 name it `deleteError`\n   2. use these properties to show\n      1. some loading text whilst the request is on its way\n      2. and some error output if the deletion should fail\n\n## 13. React Query Advantages In Action\n\n1. when clicking on the `Edit` button from the `EventDetails` page and the modal is opened, prepopulate the modal with the event data to which it belongs\n2. in `EditEvent.jsx`, load the data that should be filled into this form as a default \u0026 pass it to the `inputData` prop in the `\u003cEventForm\u003e` component with help of `useQuery`\n3. execute `useQuery()` \u0026 configure it\n4. get back the object from `useQuery()` \u0026 pull out the needed properties from it, like `data`, `isPending`, `isEror` \u0026 `error`\n5. use `data` to prepopulate this form by setting `inputData={data}`\n6. use `isPending` to show a loading indicator (`LoadingIndicator`) if we're still waiting for a response\n7. use `isError` \u0026 `error` to show an error block (`\u003cErrorBlock\u003e`) if we get an error\n8. use `data` to show the `\u003cEventForm\u003e`\n\n## 14. Updating Data with Mutations\n\nwork on this update functionality so that you can update an event from the modal after clicking the `Edit` button\n\n1. update the `http.js` file which now includes an `updateEvent` function\n2. in `EditEvent.jsx`, you need a mutation to send a request to the backend that changes the event data\n   1. create a mutation with `useMutation()`\n   2. trigger it by calling the `mutate()` property \u0026 targetting the `updateEvent` function from inside the `handleSubmit()`\n   3. in `mutate()`, pass this object with the to be forwarded data to `updateEvent` to this `mutate()` function\n      1. an object that has `id` \u0026 `event` properties\n      2. hence pass `{id: params.id, event: formData}`\n   4. call `navigate()` right after `mutate()` inside `handleSubmit()`\n\n## 15. Optimistic Updating\n\n- Do optimistic updating so that, when you press the `Update` button, the UI is updated instantly without waiting for the response of the backend\n- \u0026 if the update fails, roll back the optimistic update you performed\n\n1. in `EditEvent.jsx`, add a new `onMutate` property to this `useMutation()` configuration object\n   1. this property wants a function as a value which will be executed right when you call `mutate()`, so before this process is done \u0026 before you got back a response\n   2. in this function, update the data that is cached by React Query, this event data that is stored behind the scenes so to say\n      1. import `queryClient` from `http.js` to change the cached data (and not to invalidate queries this time)\n      2. inside the `onMutate` function, get the currently stored data so that we can manipulate it \u0026 edit it without waiting for a response with help of `queryClient.setQueryData()`\n         1. `setQueryData()` needs 2 arguments\n         2. the key of the query that you want to edit `['events', params.id]`\n         3. the new `data` you want to store under that query key, which is, in this case, that updated event data `formData` which you also sent to your backend that you can store in a `newEvent` constant\n      3. inside `onMutate()`, use `queryClient.cancelQueries()` to cancel all active queries for a specific key\n2. to make sure you can roll back your optimistic update if it fails on the backend\n   1. you also need to get the old data \u0026 store it somewhere so that you can roll back to that old data\n      1. do that before you update the data (`queryClient.setQueryData()`) with help of `queryClient.getQueryData()` which gives you the currently stored query data\n      2. store it in a `previousEvent` constant\n   2. roll back to `prevousEvent` if your update mutation failed by adding a new `onError` property to this`useMutation()` configuration object\n      1. this property wants a function which will be executed if this `mutationFn: updateEvent` function fails\n      2. it receives a couple of inputs that are passed in automatically by React Query\n         1. `error`, `data` \u0026 `context` objects\n         2. this `context` object can contain this `previousEvent`\n      3. in order for this `previousEvent` to be part of this `context`,\n         1. you should return an object in this `onMutate` function, because this object will be this `context`\n         2. and store this `previousEvent` inside of this object\n      4. on `onError`, call `queryClient.setQueryData()` again,\n         1. to again manually update the stored data for this query with this key `['events', params.id]`\n         2. but now, set it back to that old event `previousEvent` which was previously stored with `context.previousEvent`\n3. make sure that whenever this mutation finished you still fetch the latest data from the backend and the data are always into sync by forcing React Query to refetch the data behind the scenes\n   1. add a `onSettled` property to this `useMutation()` configuration object\n   2. this property also wants a function as a value\n   3. this function will be called whenever this `mutationFn: updateEvent` function is done no matter if it failed or succeeded\n   4. in that case, to be sure that you got the same data in your frontend as you have in your backend,\n      1. you should use `queryClient.invalidateQueries(['events'], params.id)`\n      2. to invalidate all the `events` queries that use this specific `params.id`\n\n## 16. Using the Query Key As Query Function Input\n\nIt would be better if we would only see some events below \"Recently added events\" instead of all events\n\n1.  in the backend `app.js` file, the backend code supports already this feature thanks to the `max` query parameter inside the get `/events` route\n    1. so you must set such a `max` query parameter on that ongoing URL to limit the number of items you're retrieving \u0026 to get the most recent items\n    2. therefore, in the frontend `http.js`, you have to tweak this `fetchEvents()` function\n       1. pull out a `max` property\n       2. tweak the URL depending on whether `max` \u0026 `searchTerm` are set, or one of the two, or none of them\n2.  in `NewEventsSection.jsx`, tweak that query so that it doesn't fetch all events but instead just some events\n    1. set this `max` property in the `queryFn: fetchEvents` function \u0026 abort `signal`\n    2. update the `queryKey` by adding to it `{max: 3}` so you have a dedicated query key for this query (like you did in `FindEventSection.jsx` with the `searchTerm`)\n    3. but, you can also use an alternative approach to avoid repetition which is\n       1. passing `queryKey` as an argument to the `queryFn` anonymous function\n       2. and using the spread operator as an argument of `fetchEvents()` to spread the object `{max: 3}` from the `queryKey` property, like this `...queryKey[1]`\n3.  use this alternative approach in `FindEventSection.jsx` for the `searchTerm`\n\n## 17. React Query \u0026 React Router\n\n- As you can see in `App.jsx`, this application uses React Router, and specifically, a version of React Router that has built-in data fetching (`loader`) \u0026 data mutation (`action`) capabilities so to say\n- you can also combine those React Router features with React Query, for example at the `EventEdit` route\n\n1. in `EditEvent.jsx`, export a `loader()` function so that you can use it to tell React Router to execute the code in this function before it loads and renders this component and then allows you to fetch data before the component even appears on the screen\n   1. to do that with React query, get access to the `queryClient` because now you won't load data through the `useQuery()` hook, but instead, since you are outside of a component function, directly with help of that `queryClient.fetchQuery` method that can be used to trigger a query programmatically, so without using the `useQuery()` hook\n      1. it takes the same configuration as `useQuery()`, so `queryKey` \u0026 `queryFn`\n      2. the `queryFn` should be the same as the one you have above in the `useQuery()`\n      3. this `loader()` function receives an object which includes a `params` property which gives access to the route parameters of this active route\n   2. return this `queryClient.fetchQuery` so that the `loader` gets this promise returned by `fetchQuery()` \u0026 waits for that promise to resolve before React Router goeas ahead and renders the component\n   3. you could think that you should now remove `useQuery()` from that component because you're using React Router now, but this is actually not the case, because whilst you could use `useLoaderData()` (a hook provided by React Router), it is better to keep `useQuery()` around, because when you use `fetchQuery()` in the `loader`, React Query will go ahead and send that request and will then store that response data in the cache. Therefore, when `useQuery()` is executed again in the component, it's this cached data that will be used\n   4. the only thing you should get rid of is this `isPending` state because you don't need this `\u003cLoadingIndicator\u003e` now\n   5. in `App.js`, import this `loader` function \u0026 connect it to this `\u003cEditEvent\u003e` route\n2. when you want to use React Router, you are not limited to fetching data, instead you can also use it for editing data, for performing mutations\n   1. back in `EditEvent.jsx`, export an `action()` async function which will be triggered by React Router when a form on this page is submitted\n      1. in this function, you will get an object passed in automatically by React Router that will have a `request` \u0026 a `params` property\n      2. inside this function, you can extract the `formData`that was submitted \u0026 get the `updatedEventData` with help of `Object.fromEntries(formData)`\n      3. send this `updatedEventData` to the backend by directly calling `updateEvent()` imported from `http.js` without creating a mutation where you will pass to it an object where you set `{ id: params.id, event: updatedEventData }` \u0026 await it to only continue once this process is completed\n      4. invalidate all queries with `queryClient.invalidateQueries(['events'])` to make sure that the updated data is fetched again, but with this alternative approach you will not perform optimistic updating anymore\n      5. return a `redirect()`\n   2. tweak the component\n      1. in `handleSubmit`, make sure that you no longer call `mutate` \u0026 `navigate()`\n      2. instead, make sure that the form is submitted so that this `action()` function is triggered, as React Router requires it\n         1. submit a form programmatically by using the `useSubmit()` hook provided by React Router which gives a `submit()` function\n         2. use this `submit()` function in `handleSubmit()` instead of `mutate` \u0026 `navigate()` to submit this form\n         3. configure this `submit()` function \u0026 set to it `{method: 'PUT'}`\n         4. this `submit()` function doesn't send an HTTP request, but only trigger this client-side `action()` function\n         5. so now, you can remove the `useMutation()` code\n   3. go back to `App.jsx` \u0026 import this `action()` function \u0026 assign it to the `\u003cEditEvent\u003e` route\n3. in `EditEvent.jsx`, use the `useNavigation()` provided by React Router hook to get the user some feedback that the request is on its way\n4. in `Header.jsx`, use the `useIsFetching()` hook provided by React Query to show some feedback with a progress bar when fetching or sending data\n5. in `EditEvent.jsx`, avoid redundant HTTP requests when using React Router in conjunction with React Query by using the `staleTime` property in the `useQuery()` function\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofiane-abou-abderrahim%2Freact-events-tanstack-query","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsofiane-abou-abderrahim%2Freact-events-tanstack-query","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofiane-abou-abderrahim%2Freact-events-tanstack-query/lists"}