What is React's useFormState and useFormStatus?
Tags: react, webdev, javascript, programming
Published: 2024-03-04 16:00:00

Thus far in our article series, we've taken a look at how React handles reactivity, server rendering, and how to send data back and forth between our React-based server and our client.

In particular, our last article outlined how we can send data from the server down to the client and back:

![The server passes down data via async server components and is passed back data via server actions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6cem5zqs9ewyl22iw2h.png)

This is a great _almost_ bi-directional communication from [React Server Components](https://unicorn-utterances.com/posts/what-are-react-server-components) and back.

> Why do you say "almost"? What's missing?

Well, once you send an action back to the server, how do you get a response back from the server? What happens if the server action needs to inform you of some status?

Well, this is where `useFormStatus` and `useFormState` come into play:

![Async data sends info to the client, server actions send it back to the server, useFormState listens for returned data from the server action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ysod4o19an2ye6gcy0s9.png)

# What is `useFormStatus`?

`useFormStatus` allows developers to listen for state changes on their React Server Actions. IE: When a server action is pending or not.

While `useFormStatus` isn't directly a way to listen for changes from the server (instead it relies on the information on the client to show its metadata) it allows us to make a nicer user experience by showing a loading indicator while the server is taking its action.

Let's start with a [client-side actions](https://unicorn-utterances.com/posts/what-are-react-server-actions#What-are-React-form-actions) demo:

```jsx
import { useFormStatus } from 'react-dom';

function Submit() {
  const status = useFormStatus();
  return (
    <button disabled={status.pending}>
      {status.pending ? 'Sending...' : 'Send'}
    </button>
  );
}

function App() {
  async function waitASecond() {
    await new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 1000);
    });
  }

  return (
    <form action={waitASecond}>
      <Submit />
    </form>
  );
}
```


Here, we're using the `pending` field on `useFormStatus` to tell us when our form is being submitted.

> **A note about `useFormStatus`:**
> You might be wondering why I've extracted the `Submit` component into its own function. This is because `useFormStatus` is a hook that implicitly gathers its state from the parent `<form>` element.
> 
> If you were to use `useFormStatus` inside of the `App` component, it would not work as expected. This is because the `App` component is not a child of the `<form>` element.
> 
> For example, the following code would not work as expected:
> ```jsx
> // This code does not work, as `useFormStatus` is not a child of the <form> element
> function App() {
>   async function waitASecond() {
>     await new Promise((resolve) => {
>       setTimeout(() => {
>         resolve();
>       }, 1000);
>     });
>   }
>   
>   const status = useFormStatus();
> 
>   return (
>     <form action={waitASecond}>
>       <button disabled={status.pending}>
>        {status.pending ? 'Sending...' : 'Send'}
>       </button>
>     </form>
>   );
> }
> ```


## `useFormStatus` usage with server actions

But of course it works with server actions as well. Let's adapt our todo list example from our last article:

```jsx
// page.jsx
import { Todo } from "./client";
import { addTodoToDatabase, getTodos } from "./todos";

export default async function Home() {
	const todos = await getTodos();

	async function addTodo(formData) {
		"use server";
		const todo = formData.get("todo");
		await addTodoToDatabase(todo);
	}

	return <Todo todos={todos} addTodo={addTodo} />;
}
```

```jsx
// client.jsx
"use client";
import { useCallback } from "react";
import { useFormStatus } from "react-dom";

function TodoFormInner() {
	const status = useFormStatus();
	return (
		<>
			{status.pending && <p>Adding todo...</p>}
			<input disabled={status.pending} name="todo" />
			<button disabled={status.pending} type="submit">
				Add Todo
			</button>
		</>
	);
}

export function Todo({ todos, addTodo }) {
	const addTodoAndRefresh = useCallback(async (formData) => {
		await addTodo(formData);
		window.location.reload();
	}, []);

	return (
		<>
			<form action={addTodoAndRefresh}>
				<TodoFormInner />
			</form>
			<ul>
				{todos.map((todo) => {
					return <li key={todo.id}>{todo.value}</li>;
				})}
			</ul>
		</>
	);
}
```


# What is `useFormState`?

`useFormState` allows us to get a response from a React Server Action and handle the results any way we might want to; including (but not limited to) displaying the contents of the response to the client.

<!-- in-content-ad title="Consider supporting" body="Donating any amount will help towards further development of articles like this." button-text="Visit our Open Collective" button-href="https://opencollective.com/unicorn-utterances" -->

This is a simple example of what `useFormState` looks like on client-side form actions:

```jsx
function App() {
  async function sayHi() {
    await new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 1000);
    });
    return 'Value from the action';
  }

  // State will be updated when `sayHi` returns a value
  const [state, action] = useFormState(sayHi, 'Initial value');

  return (
    // Pass the action from `useFormState`
    <form action={action}>
      <p>{state}</p>
      <button>Submit</button>
    </form>
  );
}
```


We can even implement a simple counter by utilizing the previous state (or initial value if there is no previous state):

```jsx
async function increment(previousState, formData) {
  return previousState + 1;
}

function App() {
  const [state, action] = useFormState(increment, 0);
  return (
    <form action={action}>
      <p>{state}</p>
      <button>Increment</button>
    </form>
  )
}
```

> This increment example comes from [the React docs for the Hook](https://react.dev/reference/react-dom/hooks/useFormState#useformstate).


## `useFormState` usage with server actions

While `useFormState` works on the client-side, it's the most useful in conjuncture with server actions.

Let's add some form validation to our todo list application so that the user can't submit an empty field:

```jsx
// page.jsx
import { Todo } from "./client";
import { addTodoToDatabase, getTodos } from "./todos";
import { redirect } from "next/navigation";

export default async function Home() {
	const todos = await getTodos();

	async function addTodo(previousState, formData) {
		"use server";
		const todo = formData.get("todo");
		if (!todo) return "Please enter a todo";
		await addTodoToDatabase(todo);
		redirect("/");
	}

	return <Todo todos={todos} addTodo={addTodo} />;
}
```

```jsx
// client.jsx
"use client";
import { useFormState } from "react-dom";

export function Todo({ todos, addTodo }) {
	const [state, action] = useFormState(addTodo, "")

	return (
		<>
			<form action={action}>
				{state && <p>{state}</p>}
				<input name="todo" />
				<button type="submit">
					Add Todo
				</button>
			</form>
			<ul>
				{todos.map((todo) => {
					return <li key={todo.id}>{todo.value}</li>;
				})}
			</ul>
		</>
	);
}
```


> **Don't forget the API changes:**
>
> Don't forget that `useFormState` requires you to change your server action to include a new first argument for `previousState`. Otherwise you'll get the following error:
>
> ```
>  ⨯ app\page.jsx (10:24) @ get
>  ⨯ TypeError: formData.get is not a function
> ```

## `useFormState` usage without client-side JavaScript

Because `useFormState` utilizes the `<form>` element's native `action` attribute under-the-hood, it works even without JavaScript enabled.

Assume you have the above sample code, but you have JavaScript disabled. When you click the submit button, the form will submit to the `action` attribute, and the page will refresh with the new information for the user.

https://unicorn-utterances.com/content/blog/what-is-use-form-state-and-status/nextjs-use-state-js-disabled.mp4

> Keep in mind that any client-side React code will not run if JavaScript is disabled. This includes the `useEffect` Hook among others.

# How to use `useFormState` and `useFormStatus` together

You may have noticed that `useFormState` doesn't provide us the same pending information that `useFormStatus` does. Let's combine them for the ultimate user experience:

```jsx
// page.jsx
import { Todo } from "./client";
import { addTodoToDatabase, getTodos } from "./todos";
import { redirect } from "next/navigation";

export default async function Home() {
	const todos = await getTodos();

	async function addTodo(previousState, formData) {
		"use server";
		const todo = formData.get("todo");
		if (!todo) return "Please enter a todo";
		await addTodoToDatabase(todo);
		redirect("/");
	}

	return <Todo todos={todos} addTodo={addTodo} />;
}
```

```jsx
// client.jsx
"use client";
import { useFormState, useFormStatus } from "react-dom";

function TodoFormInner() {
	const status = useFormStatus();
	return (
		<>
			{status.pending && <p>Adding todo...</p>}
			<input disabled={status.pending} name="todo" />
			<button disabled={status.pending} type="submit">
				Add Todo
			</button>
		</>
	);
}

export function Todo({ todos, addTodo }) {
	const [state, action] = useFormState(addTodo, "");

	return (
		<>
			<form action={action}>
				{state && <p>{state}</p>}
				<TodoFormInner />
			</form>
			<ul>
				{todos.map((todo) => {
					return <li key={todo.id}>{todo.value}</li>;
				})}
			</ul>
		</>
	);
}
```


# Conclusion

And with that, that's our article series! We've covered a lot of ground; everything from how React's most fundamental values like reactivity works to advanced bi-directional data flow from the server and back!

Want to keep learning React from lil' ole' me? Well shucks; first thank you for reading!

But I can oblige! [I've written a book called "The Framework Field Guide" that teaches React, Angular, and Vue all-at-once, in-depth, and for free](https://framework.guide). Check it out and let me know what you think.