Thursday, June 26, 2025

Virtual Threads in Java

In this tutorial we'll see what are virtual threads in Java which are added in Java 21.

Platform threads

Traditional threads which we created till now by implementing Runnable, extending Thread class or by using Executor Service to create thread pools are now known as Platform threads. So, you can say now we have two types of threads which you can create.

  1. Platform Thread
  2. Virtual Thread

When a thread is started, it is scheduled by JVM to run (Thread goes to runnable state) but actual execution of the thread is done by the underlying OS by mapping the platform thread to OS thread. A platform thread is implemented as a thin wrapper around an operating system (OS) thread which means number of available platform threads is limited to the number of OS threads. In other words, you can say scalability of platform threads is limited by the number of available OS threads.

Virtual threads in Java

Virtual threads are also an instance of java.lang.Thread so that way these threads are similar to platform threads. How they differ from platform thread is that virtual threads are implemented by the Java runtime rather than the OS.

Unlike platform thread a virtual thread isn't tied to a specific OS thread which means number of virtual threads is not limited by OS threads. You can have a lot of active virtual threads, even millions, running in the same Java process.

That is the advantage of virtual threads, they provide scale (higher throughput).

How does Virtual thread execute

A virtual thread still runs code on an OS thread, whole process goes as explained below-

  1. The Java runtime schedules a virtual thread
  2. Java runtime assigns or mounts the virtual thread on a platform thread. This platform thread is called a carrier
  3. The underlying OS schedules that platform thread by mapping it to OS thread.
  4. Virtual thread starts its execution. If the mounted virtual thread is performing a blocking I/O operation it can unmount from its carrier.
  5. Now the carrier is free and Java runtime scheduler can mount a different virtual thread on it.

When to use Virtual threads

Platform threads generally have a large thread stack and other resources that are maintained by the OS whereas virtual threads typically have a shallow call stack which means virtual threads are light weight but that doesn't mean they are any faster than platform threads.

What virtual threads provide is scale not speed. Virtual threads are suitable for running tasks that spend most of the time blocked, often waiting for I/O operations to complete. However, they aren't intended for long-running CPU-intensive operations.

How to create Virtual threads in Java

1. Using startVirtualThread(Runnable task) method

In Thread class there is a static method startVirtualThread(Runnable task) which creates a virtual thread to execute a task and schedules it to execute.

public class VTDemo {
  public static void main(String[] args) {
    Runnable r = () -> System.out.println("Running a task"); 

    Thread virtualThread = Thread.startVirtualThread(r);
    
    try {
      virtualThread.join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

2. Using ofVirtual() method of Thread class

This method returns a builder for creating a virtual Thread or ThreadFactory that creates virtual threads.

public class VTDemo {
  public static void main(String[] args) {
    Runnable r = () -> System.out.println("Running a task"); 
    Thread virtualThread = Thread.ofVirtual().start(r);
    try {
      virtualThread.join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

Thread.Builder interface has two subinterfaces

  1. Thread.Builder.OfPlatform- Builder of platform threads.
  2. Thread.Builder.OfVirtual- Builder of virtual threads.

Thread.Builder example

public class VTDemo {
  public static void main(String[] args) {
    Runnable r = () -> System.out.println("Running a task"); 
    Thread.Builder builder = Thread.ofVirtual();
    // set the name for threads
    builder.name("worker-", 0);
    Thread t1 = builder.start(r);   
    Thread t2 = builder.start(r);   
    try {
      t1.join();
      t2.join();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
    System.out.println(t1.getName() + " terminated");
    System.out.println(t2.getName() + " terminated");
  }
}

ThreadFactory of virtual thread example

Thread.Builder has a factory() method that returns a ThreadFactory to create threads from the current state of the builder.

Executors class has methods like newFixedThreadPool(), newCachedThreadPool() which take ThreadFactory as parameter.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;

public class VTFactoryDemo {
  public static void main(String[] args) {
    ThreadFactory virtualThreadFactory = Thread.ofVirtual().factory();
    ExecutorService es = Executors.newFixedThreadPool(5, virtualThreadFactory);
    Callable<String> c = () -> "Call from thread";
    Future<String> f =  es.submit(c);
    try {
      System.out.println(f.get());
    } catch (InterruptedException | ExecutionException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    es.shutdown();
  }
}

3. Executors.newVirtualThreadPerTaskExecutor() Method

This method creates an Executor that starts a new virtual Thread for each task. The number of threads created by the Executor is unbounded.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class VTFactoryDemo {
  public static void main(String[] args) throws InterruptedException, ExecutionException {
    try (ExecutorService es = Executors.newVirtualThreadPerTaskExecutor()) {
      Callable<String> c = () -> "Call from thread";
      Future<String> f =  es.submit(c);
      System.out.println(f.get());  
    }
  }
}

Source: https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html

That's all for this topic Virtual Threads in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Java ThreadLocal Class With Examples
  2. Volatile Keyword in Java With Examples
  3. Lock Striping in Java Concurrency
  4. CompletableFuture in Java With Examples
  5. Java ArrayBlockingQueue With Examples

You may also like-

  1. Java Nested Class And Inner Class
  2. CopyOnWriteArrayList in Java With Examples
  3. Java Sealed Classes and Interfaces
  4. Why no Multiple Inheritance in Java
  5. Nested Route in React Router With Example
  6. String Slicing in Python
  7. ServiceLocatorFactoryBean in Spring
  8. Angular Event Binding With Examples

Monday, June 23, 2025

Spring Boot + Spring Security JWT Authentication Example

In this tutorial we’ll see how to create a Spring Boot application that uses Spring Security and JWT token based authentication to bring authentication and authorization to the exposed REST APIs. DB used is MySQL.

What does JWT do

JWT (JSON Web Token) is used for securing REST APIs.

In the JWT authentication process a client application first need to authenticate using credentials. The server side verifies the sent credentials, if valid then it generates and returns a JWT.

Once the client has been authenticated it has to sent the token in the request’s Authorization header in the Bearer Token form with each request. The server will check the validity of the token to verify the validity of the client and authorize or reject requests. You can also store roles and method usage will be authorized based on the role.

You can also configure the URLs that should be authenticated and those that will be permitted without authentication.

Spring Boot + Spring Security with JWT authentication example

In the application we’ll have the user signup and user login logic. Once the signup is done user should be authenticated when logging in, that configuration would be done using Spring security and JWT.

Thursday, June 19, 2025

useFetcher() Hook in React Router With Example

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-

  1. fetcher.state- Available states are "idle", "loading", "submitting".
  2. fetcher.data- The data returned from the called action or loader
  3. fetcher.Form- Form used to submit data to a route action without navigating away from the current page.
  4. fetcher.submit- To programmatically submit data to a route action without navigating away from the current page.
  5. 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.

useFetcher() Hook in React Router

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-

  1. useFetcher is used here to avoid navigation away from the current path.
  2. Using object destructuring data and state from fetcher are extracted to two variables.
  3. 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.
  4. It is actually fetcher.Form which causes the action function to be called without navigating away from the current page. By changing it to
    you can see that it navigates to the “/favPost” path then.

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

  1. CRUD Example With React Router Loader and Action
  2. Data Loader With Dynamic Routes in React
  3. Actions in React Router For Data Mutation
  4. useRouteLoaderData() Hook in React Router
  5. useSearchParams in React Router - Handling Query Parameters

You may also like-

  1. JVM Run-Time Data Areas - Java Memory Allocation
  2. Difference Between Abstract Class And Interface in Java
  3. Armstrong Number or Not Java Program
  4. Spring Boot + Data JPA + MySQL REST API CRUD Example

CRUD Example With React Router Loader and Action

In the post- Actions in React Router For Data Mutation we saw how to handle data mutation and add a new record using action function. In this post we'll see a complete CRUD example using loader and action in React Router.

Add, update, delete and fetch data using React Router

This example shows how you can load post data and also how you can do post data mutation (adding, modifying or deleting). For API call https://jsonplaceholder.typicode.com/posts resource is used which is a free fake API. In the example Bootstrap 5 is used for styling.

Routing Definition

Routing configuration for the CRUD example is as given below.

src\components\routes\Route.js

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 PostDetails, {loader as postDetailsLoader} from "./PostDetails";
import PostLayout from "./PostLayout";
import PostEdit from "./PostEdit";
import PostAdd from "./PostAdd";
import {addEditAction as postAddEditAction, deleteAction as postDeleteAction} from "./PostActions";

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: ":postId", id: "post-detail", loader: postDetailsLoader,
       children: [
          {index:true, element: <PostDetails />, action: postDeleteAction, hydrateFallbackElement: <h2>Loading...</h2>},
          {path:"edit", element: <PostEdit />, action: postAddEditAction},
       ]
      },
      {path:"new", element: <PostAdd />, action: postAddEditAction}
     ]
    },
   ]
  },
])

Here we have 3 levels of nested routes. Root parent is Navigation menu component. Another level is for PostLayout component for path "/post" which renders the list of posts, yet another level of nested route is for dynamic path "/post/POST_ID" where we have operations to fetch, delete or edit post by ID.

Path "/post/new" is for creating a resource.

Notice the use of loader and action properties to specify the needed loader and action functions.

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 posts data.

import { Link, useLoaderData, useNavigate } from "react-router";

const PostList = () => {
  const postData = useLoaderData();
  const navigate = useNavigate();
  const PostAddHandler = () => {
    navigate("./new");
  }
  return(
    <> 
      <div>
        <button className="btn btn-info" onClick={PostAddHandler}>Add New Post</button>
      </div>

      <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} <Link to={post.id.toString()}>{post.title}</Link>  
        </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;
  }
}

Some of the points to note here-

  1. In the component, loader function named loader is written.
  2. Uses fetch API to get data from the given URL. Only 10 posts are fetched.
  3. Data fetched by the loader function is retrieved using the useLoaderData() hook.
  4. That post data is then iterated and link with postID is created with each post so that clicking any post leads to rendering of details for the post.
  5. There is also a button to add new post. Clicking that button results in navigation to “/new” relative to current route path.

src\components\routes\PostDetails.js

This component renders the post data by ID.

import { Form, useNavigate, useRouteLoaderData, useSubmit } from "react-router";

const PostDetails = () => {
  const postData = useRouteLoaderData("post-detail");
  const navigate = useNavigate();
  const submit = useSubmit();
  const editHandler = () => {
    navigate("./edit");
  }
  const deleteHandler = () => {
    const flag = window.confirm("Do you really want to delete");
    if(flag){
      submit(null, {method:"DELETE"});
    }
  }
  return(
    <>
      <h2 className="text-center">{postData.title}</h2>   
      <div>{postData.body}</div>
      <div className="text-center">
        <button className="btn btn-success" onClick={editHandler}>Edit</button> 
        <button className="btn btn-danger mx-2" onClick={deleteHandler}>Delete</button>
      </div>
    </>
  )
}

export default PostDetails;

export async function loader({request, params}){
  const url = "https://jsonplaceholder.typicode.com/posts/"+params.postId
  const response = await fetch(url);
  //console.log(response);
  if (!response.ok) {
    throw new Response('Error while fetching post data', {status:404});
  }
  else{
    const responseData = await response.json();
    return responseData;
  }
}

Some of the points to note here-

  1. In the component, loader function named loader is written which is used to fetch data by post Id.
  2. In loader function you can’t use useParams() hook to get the route parameters. Router passes an object to the loader function that has two properties- request and params.
  3. Using request you can access the request body, request URL etc. Using params you can access the route path parameters.
  4. Data fetched by the loader function is retrieved by using the useLoaderData() hook. In this case data is post data for a particular ID.
  5. Post title and post body are rendered by this component.
  6. There are two buttons also “edit” and “delete”. Clicking “edit” button results in route “/post/POST_ID/edit”. Clicking on “delete” button asks for confirmation first.
  7. If delete is confirmed then the delete request is submitted programmatically using useSubmit() hook. Read more about useSubmit() hook in this post- useSubmit() Hook in React Router
Update with React Router

src\components\routes\PostActions.js

This JavaScript file has action functions for add/edit and delete.

import { redirect } from "react-router";

export async function addEditAction({request, params}){
  const data = await request.formData();
  const postId = params.postId;
  const postData = {
    title:data.get("title"),
    body:data.get("body"),
    userId:data.get("userId"),
    // add id if required -- Only for edit
    ...(postId && { id: postId })
  } 
  //console.log('POST data', postData);
  const method = request.method;

  let url = 'https://jsonplaceholder.typicode.com/posts';
  if(method === "PUT" || method === "PATCH"){
    url = url+"/"+postId;
    console.log(url);
  }
  const response = await fetch(url, 
    {
      method: method, 
      headers: {                
        'Content-type': 'application/json',                 
      },
      body: JSON.stringify(postData)
    },
      
  );
  if(!response.ok){
      throw new Response('Error while saving post data', {status:500});
  } 
  const responseData = await response.json();
  console.log(responseData);
  return redirect('/post');
}

export async function deleteAction({request, params}){
  let url = 'https://jsonplaceholder.typicode.com/posts/'+params.postId;
  console.log('DELETE', url);
  const method = request.method;
  console.log('Method', method);
  const response = await fetch(url, {
    method: method
  });
  if(!response.ok){
    throw new Response('Error while deleting post data', {status:500});
  } 
  const responseData = await response.json();
  //console.log(responseData);
  return redirect('/post');
}

Some of the points to note here-

  1. Action function receives an object that has properties like request, params.
  2. Request object has a method formData() that returns form data.
  3. Using params you can access the route path parameters.
  4. In deleteAction function postID is extracted from the URL using params and added to the URL which is sent to delete the specific post.
  5. Fetch API is used to make HTTP requests.
  6. For adding and editing, same function is used with some conditional changes like post data that is sent as part of request body includes postID in case of edit where as for add it doesn’t include postID.
  7. URL also is created conditionally, in case of edit postID has to be added as path parameter
  8. With action function or loader function it is recommended to use redirect function rather than useNavigate to navigate to another page. That’s what is done here, if operation completes successfully, redirect to “/post” path.
Delete with React Router

src\components\routes\PostAdd.js

This component is called when the route path is "/post/new".

import PostForm from "./PostForm"

const PostAdd =() => {
  return(
    <PostForm method="POST"/>
  )
}

export default PostAdd;

src\components\routes\PostEdit.js

This component is rendered for editing post. In this component post data (by id) is retrieved using useRouteLoaderData() hook and that data is passed to another component PostForm as a prop.

Why useRouteLoaderData() hook is used here. To understand that refer this post- useRouteLoaderData() Hook in React Router

import { useRouteLoaderData } from "react-router";
import PostForm from "./PostForm";

const PostEdit = () => {
    const postData = useRouteLoaderData("post-detail");
    return(
        <PostForm postData={postData} method="PUT" />
    )
}

export default PostEdit;

src\components\routes\PostForm.js

Form component which is rendered for both adding or editing a post. When editing, Post form is pre-filled with existing post data.

import { Form, useNavigation } from "react-router";

const PostForm = ({postData, method}) => {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === 'submitting';

  return(
    <div className="container">
      <h2>Post</h2>
      <Form method={method}> 
        {(method === "PUT" || method === "PATCH")?
        <div className="mb-2 mt-2">
          <label className="form-label" htmlFor="id">ID: </label>
          <input className="form-control" type="text" name="id" id="id" disabled={true} defaultValue={postData?postData.id:""}></input>
        </div>
        :""}
        <div className="mb-2">
          <label className="form-label" htmlFor="title">Title: </label>
          <input className="form-control" type="text" name="title" id="title" defaultValue={postData?postData.title:""}></input>
        </div>
        <div className="mb-2">
          <label className="form-label" htmlFor="body">Body: </label>
          <textarea className="form-control" type="text" name="body" id="body" rows="3" defaultValue={postData?postData.body:""}></textarea>
        </div>
        <div className="mb-2">
          <label className="form-label" htmlFor="userId">User ID: </label>
          <input className="form-control" type="text" name="userId" id="userId"  defaultValue={postData?postData.userId:""}></input>
        </div>
        <button className="btn btn-info" type="submit" disabled={isSubmitting}>Save</button>
      </Form>
    </div>
  )
}

export default PostForm;

Some of the points to note here-

  1. While using PostForm component, method is passed as a prop. In PostAdd method is passed as “POST” whereas with PostEdit it is passed as “PUT”.
  2. Whether “id” field should be displayed or not is decided on the basis of method. For add it is not displayed as new postID will be created for the newly added post. For edit existing postID is displayed.
  3. Navigation state is also accessed using the useNavigation() hook. Using that “Save” button is disabled once the form is submitted to avoid multiple clicks for the same post.
Edit form

That's all for this topic CRUD Example With React Router Loader and Action. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Data Loader With Dynamic Routes in React
  2. useRouteLoaderData() Hook in React Router
  3. Using NavLink in React Router
  4. Index Route in React Router

You may also like-

  1. JVM Run-Time Data Areas - Java Memory Allocation
  2. Difference Between Abstract Class And Interface in Java
  3. Armstrong Number or Not Java Program
  4. Spring Boot + Data JPA + MySQL REST API CRUD Example

Tuesday, June 17, 2025

useSubmit() Hook in React Router

In the post Actions in React Router For Data Mutation we saw how you can create a resource (POST method) using actions and Form component in React Router. Another way (more explicit way) to submit a form is to use useSubmit() hook in React Router. In this post we'll see how to programmatically submit a form using useSubmit() hook.

useSubmit() hook in React Router

useSubmit hook returns a function.
const submit = useSubmit();

This returned function (named submit here) takes two arguments. First is the data that has to be submitted which will be wrapped as form data object. Another argument is an optional object where you can pass method with which you can specify the HTTP verb to use when the form is submitted. Supports "get", "post", "put", "delete", and "patch". Another property you can pass is action with which you can provide the URL to submit the form data to. If no action is specified, this defaults to the closest route in context.

submit(event.currentTarget, {method:"POST", action:"/post/new"});

useSubmit() hook React example

In this example we'll use an action function to create a resource. For API call https://jsonplaceholder.typicode.com/posts resource is used which is a free fake API. In the example Bootstrap 5 is used for styling. This example is same as what is already shown in this post Actions in React Router For Data Mutation except the change for programmatically submitting the form having post data using useSubmit() hook.

Component that changes is the PostForm component. Now rather than using the Form component normal <form> element is used. On submitting the form handler function is called that submits the form programmatically.

src\components\routes\PostForm.js

import { useSubmit } from "react-router";

const PostForm = () => {
   
  const submit = useSubmit();
  const formSubmitHandler = (event) => {
    event.preventDefault();
    //console.log(event.currentTarget);
    submit(event.currentTarget, {method:"POST", action:"/post/new"});
  }
  return(
    <div className="container">
      <h2>Post</h2>
      <form onSubmit={formSubmitHandler}> 
        {/* <div className="mb-2 mt-2">
            <label className="form-label" htmlFor="id">ID: </label>
            <input className="form-control" type="text" name="id" id="id" defaultValue={postData?postData.id:""}></input>
        </div> */}
        <div className="mb-2">
          <label className="form-label" htmlFor="title">Title: </label>
          <input className="form-control" type="text" name="title" id="title"></input>
        </div>
        <div className="mb-2">
          <label className="form-label" htmlFor="body">Body: </label>
          <textarea className="form-control" type="text" name="body" id="body"></textarea>
        </div>
        <div className="mb-2">
          <label className="form-label" htmlFor="userId">User ID: </label>
          <input className="form-control" type="text" name="userId" id="userId" rows="3"></input>
        </div>
        <button className="btn btn-info" type="submit">Save</button>
      </form>
    </div>
  )
}

export default PostForm;

In the handler function (formSubmitHandler()) for form submit, function returned by useSubmit hook is used to submit data. In the submit function first argument is event.currentTarget which is the form itself. As the second argument an object is passed with method and action properties. Here method value is POST and action is "/post/new" path where the action function is defined. If we don't specify action path here that will also work because action defaults to the closest route in context which is anyway "/post/new".

src\components\routes\PostAdd.js

This component is called when the route path is "/post/new".

import { redirect } from "react-router";
import PostForm from "./PostForm"

const PostAdd =() => {
  return(
    <PostForm />
  )
}

export default PostAdd;

export async function action({request, params}){
  const data = await request.formData();
  const postData = {
    title:data.get("title"),
    body:data.get("body"),
    userId:data.get("userId")
  }
  const response = await fetch('https://jsonplaceholder.typicode.com/posts', 
    {
      method: 'POST', 
      headers: {                
          'Content-type': 'application/json',                 
      },
      body: JSON.stringify(postData)
    },
      
  );
  if(!response.ok){
    throw new Response('Error while saving post data', {status:500});
  } 
  const responseData = await response.json();
  //console.log(responseData);
  return redirect('/post');
}

Some of the points to note here-

  1. Action function receives an object that has properties like request, params.
  2. Request object has a method formData() that returns form data.
  3. On that data object you can call get() method to get the form field value by passing the form field name. The name you pass in get method should match the name you gave in the input element with in the form. Refer PostForm to check the name attribute in each input element.
  4. With action function or loader function it is recommended to use redirect function rather than useNavigate to navigate to another page. That’s what is done here, if post is added successfully redirect to “/post” path.

That's all for this topic useSubmit() Hook in React Router. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Data Loader With Dynamic Routes in React
  2. useRouteLoaderData() Hook in React Router
  3. Using NavLink in React Router
  4. Index Route in React Router

You may also like-

  1. JVM Run-Time Data Areas - Java Memory Allocation
  2. Difference Between Abstract Class And Interface in Java
  3. Armstrong Number or Not Java Program
  4. Spring Boot + Data JPA + MySQL REST API CRUD Example

Actions in React Router For Data Mutation

In the post Data Loader in React Router we have seen how to fetch data using loaders in React Router. Same way action in React Router is a function that handles data mutations.

If you have a CRUD operation then Create, Delete and Update part (data mutation operations) can be done by actions. For read operation (data fetching) you can use loader.

How to call actions

You can execute action function by using one of the following ways.

  1. Actions are called declaratively through Form component provided by React Router. That is what we'll see in this article
  2. Imperatively through useSubmit() hook
  3. By using fetcher.Form or fetcher.submit

Using action to Post data- React Example

In this example we'll use an action function to create a resource. For API call https://jsonplaceholder.typicode.com/posts resource is used which is a free fake API. In the example Bootstrap 5 is used for styling.

Route configuration

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:"new", element: <PostAdd />, action: postAddAction}
     ]
    },
   ]
  },
])

Here we have 2 levels of nested routes. Root parent is Navigation menu component. Another level is for PostLayout component for path "/post" which renders the list of posts and another nested route definition for "/post/new" to create a new post.

Notice the use of action property with route definition for "/post/new".

{path:"new", element: <PostAdd />, action: postAddAction}

That points to the action function called to add post data.

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 a parent route component for all the Post related functionality.

import { Outlet } from &quot;react-router&quot;;
const PostLayout = () =&gt; {
    return(
        &lt;div className=&quot;mx-2&quot;&gt;
            &lt;Outlet /&gt;
        &lt;/div&gt;

    )
}

export default PostLayout;

src\components\routes\PostList.js

This component lists all the fetched post data.

import { Link, useLoaderData, useNavigate } from "react-router";

const PostList = () => {
  const postData = useLoaderData();
  const navigate = useNavigate();
  const PostAddHandler = () => {
    navigate("./new");
  }
  return(
    <> 
      <button className="btn btn-info" onClick={PostAddHandler}>Add New Post</button>
      <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} <Link to={post.id.toString()}>{post.title}</Link>  
        </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;
  }
}

Some of the points to note here-

  1. In the component, loader function named loader is written.
  2. Uses fetch API to get data from the given URL. Only 10 posts are fetched.
  3. Data fetched by the loader function is retrieved using the useLoaderData() hook. You can use useLoaderData here as loader data is needed with in the current route.
  4. There is also a button to add new post. Clicking that button results in navigation to “/new” relative to current route path.
Actions in React Router

src\components\routes\PostForm.js

This component renders a form to enter data for new post.

import { Form } from "react-router";

const PostForm = () => {
  return(
    <div className="container">
      <h2>Post</h2>
      <Form method="post"> 
        <div className="mb-2">
          <label className="form-label" htmlFor="title">Title: </label>
          <input className="form-control" type="text" name="title" id="title"></input>
        </div>
        <div className="mb-2">
          <label className="form-label" htmlFor="body">Body: </label>
          <textarea className="form-control" type="text" name="body" id="body"></textarea>
        </div>
        <div className="mb-2">
          <label className="form-label" htmlFor="userId">User ID: </label>
          <input className="form-control" type="text" name="userId" id="userId" rows="3"></input>
        </div>
        <button className="btn btn-info" type="submit">Save</button>
      </Form>
    </div>
  )
}

export default PostForm;

Some of the points to note here-

  1. Form component used here is provided by React Router which is a progressively enhanced HTML <form> that submits data to actions. When the form is submitted form data is provided to the action.
  2. With Form component you can use props like action and submit.
  3. With action prop you can provide the URL to submit the form data to. If no action is specified, this defaults to the closest route in context.
  4. With method prop you can specify the HTTP verb to use when the form is submitted. Supports "get", "post", "put", "delete", and "patch".

src\components\routes\PostAdd.js

This component is called when the route path is "/post/new".

import { redirect } from "react-router";
import PostForm from "./PostForm"

const PostAdd =() => {
  return(
    <PostForm />
  )
}

export default PostAdd;

export async function action({request, params}){
  const data = await request.formData();
  const postData = {
    title:data.get("title"),
    body:data.get("body"),
    userId:data.get("userId")
  }
  const response = await fetch('https://jsonplaceholder.typicode.com/posts', 
    {
      method: 'POST', 
      headers: {                
          'Content-type': 'application/json',                 
      },
      body: JSON.stringify(postData)
    },
      
  );
  if(!response.ok){
    throw new Response('Error while saving post data', {status:500});
  } 
  const responseData = await response.json();
  console.log(responseData);
  return redirect('/post');
}

Some of the points to note here-

  1. Action function receives an object that has properties like request, params.
  2. Request object has a method formData() that returns form data.
  3. On that data object you can call get() method to get the form field value by passing the form field name. The name you pass in get method should match the name you gave in the input element with in the form. Refer PostForm to check the name attribute in each input element.
  4. With action function or loader function it is recommended to use redirect function rather than useNavigate to navigate to another page. That’s what is done here, if post is added successfully redirect to “/post” path.
data mutation in React Router

That's all for this topic Actions in React Router For Data Mutation. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. useRouteLoaderData() Hook in React Router
  2. Search Filter Using useSearchParams in React Router
  3. Setting 404 Error Page With React Router
  4. Lazy Loading Routes in React

You may also like-

  1. Controlled and Uncontrolled Components in React
  2. JavaScript Array slice() method With Examples
  3. Java Sealed Classes and Interfaces
  4. Java split() Method - Splitting a String
  5. Accessing Characters in Python String
  6. Angular Route Resolver - Passing Data Dynamically

useRouteLoaderData() Hook in React Router

In this post we'll see how to use useRouteLoaderData() hook in React Router to access data loaded by loader function. useRouteLoaderData() hook makes data available any where within the component hierarchy, that's how it differs from another similar hook useLoaderData() which can access data from the closest route loader.

The benefit of useRouteLoaderData() hook is that it enables sharing of data across components. Component can access data from parent route or sibling routes.

How to use useRouteLoaderData in React Router

You need to first import it.

import { useRouteLoaderData } from "react-router";

You need to pass route ID of the given route to useRouteLoaderData() and it returns loader data for that route. For example if you have defined route as given below with id property.

{path: ":postId", id: "post-detail", loader: postDetailsLoader}

Then you can use it like this with useRouteLoaderData to get data fetched by postDetailsLoader loader function.

const postData = useRouteLoaderData("post-detail");

Route IDs are created automatically too. They are simply the path of the route file relative to the app folder, so you can use, automatically created id also rather than creating one manually.

useRouteLoaderData() hook React example

For API call https://jsonplaceholder.typicode.com/posts resource is used which is a free fake API. In the example Bootstrap 5 is used for styling.

Let's try to understand how useRouteLoaderData() hook works with an example. Suppose we have the following route configuration.

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: ":postId", id: "post-detail", loader:PostDetailsLoader,
           children: [
              {index:true, element: <PostDetails />, hydrateFallbackElement: <h2>Loading...</h2>},
              {path:"edit", element: <PostEdit />}
           ]
        },
        {path:"new", element: <PostAdd />, action:PostAddAction}
     ]
    },
   ]
  },
])

Here we have 3 levels of nesting. Root parent is Navigation menu component. Another level is for PostLayout component for path "/post" which renders the list of posts.

Yet another level is dynamic route for "/post/POST_ID" which renders details for the selected post through PostDetails component. There is another child with in the same hierarchy to render a form to edit the selected post, for that path is "post/POST_ID/edit".

When you are showing edit form for editing a post, you want that form pre-filled with the selected post data (fetch post by id). Same way when you are going to post details page that shows data for the selected post. Since both of these components need data by postId, so the loader function can be shared. If you notice the route configuration, that's what has been done, loader is configured at the parent level.

{path: ":postId", id: "post-detail", loader:PostDetailsLoader,
  children: [
    {index:true, element: <PostDetails />, hydrateFallbackElement: <h2>Loading...</h2>},
    {path:"edit", element: <PostEdit />}
  ]
},

With the above route configuration, trying to get loader data using useLoaderData() hook won't work as that can get data only from the closest route loader where as in this case we need data from the parent route. In this kind of scenario useRouteLoaderData() hook can be used as that can get data from the parent route.

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 a parent route component for all the Post related functionality.

import { Outlet } from &quot;react-router&quot;;
const PostLayout = () =&gt; {
    return(
        &lt;div className=&quot;mx-2&quot;&gt;
            &lt;Outlet /&gt;
        &lt;/div&gt;

    )
}

export default PostLayout;

src\components\routes\PostList.js

This component lists all the fetched post data.

import { Link, useLoaderData } from "react-router";

const PostList = () => {
    const postData = useLoaderData();
    return(
      <> 
        <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} <Link to={post.id.toString()}>{post.title}</Link>  
            </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;
  }
}

Some of the points to note here-

  1. In the component, loader function named loader is written.
  2. Uses fetch API to get data from the given URL. Only 10 posts are fetched.
  3. If there is no error in fetching data (response is ok) then you can return data directly with React Router V7.
  4. Data fetched by the loader function is retrieved by using the useLoaderData() hook. You can use useLoaderData here as loader data is needed with in the current route.
  5. Post data is then iterated to print id and title of each post. With each post a Link with its id is also created
useRouteLoaderData() Hook in React Router

src\components\routes\PostDetails.js

This component renders details of the post whose ID is selected. Also shows buttons to edit or delete post.

import { useNavigate, useRouteLoaderData } from "react-router";

const PostDetails = () => {
  const postData = useRouteLoaderData("post-detail");
  const navigate = useNavigate();
  const editHandler = () => {
      navigate("./edit");
  }
  return(
    <>
      <h2 className="text-center">{postData.title}</h2>
      <div>{postData.body}</div>
      <div className="text-center">
        <button className="btn btn-success " onClick={editHandler}>Edit</button> 
        <button className="btn btn-danger mx-2">Delete</button>
      </div>
    </>
  )
}

export default PostDetails;

export async function loader({request, params}){
  const url = "https://jsonplaceholder.typicode.com/posts/"+params.postId
  const response = await fetch(url);
  console.log(response);
  if (!response.ok) {
      throw new Response('Error while fetching post data', {status:404});
  }
  else{
      const responseData = await response.json();
      return responseData;
  }
}

Some of the points to note here-

  1. In the component, loader function named loader is written which is used to fetch data by post Id.
  2. Data fetched by the loader function is retrieved using the useRouteLoaderData() hook. That is needed as loader though defined here is actually called at the parent route level
    {path: ":postId", id: "post-detail", loader: postDetailsLoader,
      children: [
        {index:true, element: <PostDetails />, hydrateFallbackElement:  <h2>Loading...</h2>},
    
  3. Clicking on Edit button navigates to /post/POST_ID/edit path
  4. Delete button functionality is not given in this post.
useRouteLoaderData() Hook

src\components\routes\PostEdit.js

This component retrieves the post data (by id) using useRouteLoaderData() hook and pass that data to another component PostForm as a prop.

import { useRouteLoaderData } from "react-router";
import PostForm from "./PostForm";

const PostEdit = () => {
  const postData = useRouteLoaderData("post-detail");
  return(
    <PostForm postData={postData} />
  )
}

export default PostEdit;
src\components\routes\PostForm.js
const PostForm = ({postData}) => {
  return(
    <div className="container">
      <h2>Post</h2>
      <form>
        <div className="mb-2 mt-2">
            <label className="form-label" htmlFor="id">ID: </label>
            <input className="form-control" type="text" name="id" id="id" defaultValue={postData?postData.id:""}></input>
        </div>
        <div className="mb-2">
            <label className="form-label" htmlFor="title">Title: </label>
            <input className="form-control" type="text" name="title" id="title" defaultValue={postData?postData.title:""}></input>
        </div>
        <div className="mb-2">
            <label className="form-label" htmlFor="body">Body: </label>
            <textarea className="form-control" type="text" name="body" id="body" defaultValue={postData?postData.body:""}></textarea>
        </div>
        <div className="mb-2">
            <label className="form-label" htmlFor="userId">User ID: </label>
            <input className="form-control" type="text" name="userId" id="userId" rows="3" defaultValue={postData?postData.userId:""}></input>
        </div>
        <button className="btn btn-info" type="submit">Save</button>
      </form>
    </div>
  )
}

export default PostForm;

That's all for this topic useRouteLoaderData() Hook in React Router. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Lazy Loading Routes in React
  2. Data Loader in React Router
  3. Data Loader With Dynamic Routes in React
  4. React Declarative Approach

You may also like-

  1. JavaScript Rest Parameter
  2. Spring Boot Observability - Distributed Tracing, Metrics
  3. Java Record Class With Examples
  4. Interface in Java With Examples
  5. Angular CanActivateChild Guard to protect Child Routes