In this post we'll see how to use useFetcher()
hook in React Router. Generally, when we have a loader or action
function any call to it also triggers a navigation.
For example, suppose we have a Form component defined with method and action prop as given below.
<Form method="post" action="/favPost">
If you are at "/post" path when this action is triggered then you will be navigated to "/favPost" path.
If you don't want to trigger this navigation; meaning you want to call the action associated with "/favPost" path but need to stay at the current path "/post" that can be done using useFetcher() hook in React Router.
The useFetcher hook in React Router allows for execution of route loaders and actions without triggering a full navigation. Any scenario where data has to be loaded or mutated in background, paginated data where you want to stay on the same page with next page data can be handled using useFetcher.
useFetcher hook in React
useFetcher hook returns an object.
const fetcher = useFetcher();
Some of the properties provided by the fetcher object are as-
- fetcher.state- Available states are "idle", "loading", "submitting".
- fetcher.data- The data returned from the called action or loader
- fetcher.Form- Form used to submit data to a route action without navigating away from the current page.
- fetcher.submit- To programmatically submit data to a route action without navigating away from the current page.
- fetcher.load- Fetching data from a loader function associated with a route without navigating away from the current page.
useFetcher hook React example
Suppose you have a scenario to show list of posts and also have an input element at the top to take user input for favourite post. While saving that favourite post you don't want to navigate to the "/favPost" route but want to stay at the post list page.
Route Configuration
import { createBrowserRouter } from "react-router"; import Home from "./home"; import NavigationNavLink from "./NavigationNavLink"; import ErrorPage from "./ErrorPage"; import PostList, {loader as postLoader} from "./PostList"; import PostLayout from "./PostLayout"; import FavPost, {action as favPostAction} from "./FavPost"; export const route = createBrowserRouter([ {path: "/", element: <NavigationNavLink />, errorElement: <ErrorPage />, children: [ {index: true, element: <Home /> }, {path: "post", element: <PostLayout />, children:[ {index: true, element: <PostList />, loader: postLoader, hydrateFallbackElement: <h2>Loading...</h2>}, ] }, {path: "favpost", element: <FavPost />, action: favPostAction} ] }, ])
Components
For Navigation Menu, Home and ErrorPage components you can get the code from this post- Data Loader in React Router
src\components\routes\PostLayout.js
This component acts as a parent route component for all the Post related functionality. Child component is rendered in the place of
Outlet
which acts as a placeholder.
import { Outlet } from "react-router"; const PostLayout = () => { return( <div className="mx-2"> <Outlet /> </div> ) } export default PostLayout;
src\components\routes\PostList.js
This component lists all the fetched post data.
import { useLoaderData } from "react-router"; import FavPost from "./FavPost"; const PostList = () => { const postData = useLoaderData(); return( <> <FavPost /> <h2 className="text-info-emphasis text-center">Posts</h2> <ul className="list-group"> {postData.map((post) => <li className="list-group-item" key={post.id}> {post.id} {post.title} </li> )} </ul> </> ) } export default PostList export async function loader(){ const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10'); // check for any error if(!response.ok){ // use Response object throw new Response("Failed to load data", { status: response.status }); }else{ const responseData = await response.json(); return responseData; } }
As you can see <FavPost> component is called with in the <PostList> component.
src\components\routes\FavPost.js
import { useEffect } from "react"; import { useFetcher } from "react-router"; const FavPost = () => { const fetcher = useFetcher(); const {data, state} = fetcher; useEffect(()=>{ if(state === "idle" && data && data.message){ window.alert(data.message); } }, [data, state]) return( <fetcher.Form method="post" action="/favPost"> <label className="form-label" htmlFor="fav">Your favorite post</label> <input type="text" id="fav" name="favpost"></input> <button className="btn btn-success" type="submit">Save</button> </fetcher.Form> ); } export default FavPost; export async function action({request}){ console.log('Saving favorite post data') const data = await request.formData(); const favpost = data.get('favpost'); // send to backend server to save favorite post... console.log(favpost); return { message: 'Saved successfully!' }; }
Some of the points to note here-
- useFetcher is used here to avoid navigation away from the current path.
- Using object destructuring data and state from fetcher are extracted to two variables.
- With in useEffect() hook there is a check that the state is “idle” (action execution is done), there is data then show the data.message.
- It is actually fetcher.Form which causes the action function to be called without navigating away from the current page. By changing it to
That's all for this topic useFetcher() Hook in React Router With Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!
Related Topics
You may also like-
No comments:
Post a Comment