React 19, handling forms using useOptimistic and useFormStatus along with React Hook Form and Zod … practical example ⚛️
Tags: react, frontend, serveractions
Published: 2024-04-07 07:33:25



The challenge here to use _useOptimistic_ to get more responsive UI with form actions along with _React Hook Form_ and _Zod_ schema (to apply client side validation) and at the same time to prevent clicking on the submit button multiple time until the current action is finished which we will handle using _useFormStatus_ hook.

<img style="width:100%" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdGNrOHBrY255b2czZTYwZHdoejNvMHJmMzgxYm95dmo2Y3BheHMyNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/QdSqpO14y0aFmdFNYK/giphy.gif">

First of all, I will give a brief explanation about the used hooks.

**useOptimistic**: allows to optimistically update the UI during async actions (like a network request) by showing different state. 
It accepts the current state which will be returned initially and whenever no action is pending and an update function that takes the current state and the input to the action.
It will returning the optimistic state to be used while waiting for the action and a dispatching function “addOptimistic” to call when we have an optimistic update.
So, when a user submits a form, instead of waiting for the server’s response to display the new changes, the interface is immediately updated with the expected outcome with a label to indicate that the action in pending “loading, adding …”and when that action in completed then this label will be removed.

**useFormStatus**: gives status information of the last form submission. 
This Hook returns information like the pending property to tell if the form is actively submitting, which we will use for example to disable buttons while the form is submitting.


**Practical example** 💻

1.
Both useOptimistic and useFormStatus are useful hooks in the upcoming React 19, for now these hooks are only available in React’s Canary and experimental channels.
To review React 19 features, we will need to install the Canary version of React and react-dom by using the following:
`npm i react@18.3.0-canary-6db7f4209-20231021  react-dom@18.3.0-canary-6db7f4209-20231021`

2.
Add Zod schema and zodResolver to integrate react-hook-form with Zod schema validation library:

```javascript
export const TaskSchema = z.object({
  details: z
    .string()
    .min(5, "Task details must be at least 5 characters")
    .max(100, "Task details must be less than 100 characters"),
  completed: z.boolean(),
  editMode: z.boolean(),
});

```


```javascript
const {
    register,
    control,
    trigger,
    reset,
    formState: { errors },
  } = useForm<TaskSchemaType>({
    resolver: zodResolver(TaskSchema),
    defaultValues: { completed: false, details: "", editMode: false },
});
```

3.
For forms we will use action to benefit from useOptimistic hook and from server actions React 19 feature:

```javascript
<form
  action={handleFormAction}
  style={{ margin: "10px 5px 20px" }}
>
  <div
    style={{
      display: "flex",
      justifyContent: "space-around",
      gap: "1",
      padding: "5px",
    }}
  >
    <div>
      <label htmlFor={`task-details-${id}`}>Task details:&nbsp;</label>
      <input
        id={`task-details-${id}`}
        type="text"
        autoComplete="off"
        {...register("details")}
      />
      {errors.details && (
        <div style={{ color: "#DC3545" }}>{errors.details.message}</div>
      )}
    </div>
    <div>
      <label htmlFor={`task-status-${id}`} className="prevent-select">
        Is completed:
      </label>
      <input
        id={`task-status-${id}`}
        type="checkbox"
        {...register("completed")}
      />
    </div>
  </div>
  <SubmitBtn label="Save task details" />
</form>
```

 

4.
In formAction we will add an optimistic update after using "trigger" method from React Hook Form to trigger client side validation:

```javascript
const handleFormAction = async (data: any) => {
    const res = await trigger(["details"]);
    if (!res) return;
    const updatedTask: Task = {
      id: task.id,
      details: data.get("details"),
      completed: data.get("completed"),
      editMode: false,
    };
    addOptimisticTask({ ...updatedTask, process: "edit", editMode: true });

    await editTask(updatedTask);

    reset({ details: "", completed: false });
  };
```

5.
We will use useOptimistic to handle different optimistic actions:
```javascript
const [optimisticTasks, addOptimisticTask] = 
useOptimistic<Array<Task>>(
    tasks,
    (state: Array<Task>, updatedTask: Task) => {
      if (updatedTask.process === "add")
        return [...state, { ...updatedTask, loading: true }];
      else if (updatedTask.process === "delete") {
        return state.map((task) =>
          task.id === updatedTask.id
            ? { ...task, loading: true, process: updatedTask.process }
            : task
        );
      } else if (updatedTask.process === "setForEdit") {
        return state;
      } else if (updatedTask.process === "edit") {
        return state.map((task) =>
          task.id === updatedTask.id
            ? { ...updatedTask,loading: true }
            : task
        );
      } else return state;
    }
  );
```

6.
To mimic slow fetching we will use the following async method:

```javascript
export const sleep=async(delayInMS: number):Promise<any>=>{
    return new Promise((resolve)=> setTimeout(resolve, delayInMS))
}
```

7.
Add SubmitBtm which will be rendered within form tag to get pending status by using useFormStatus hook: 

```javascript
import React from "react";
import { useFormStatus } from "react-dom";

const SubmitBtn = ({ label }: { label: string }) => {
  const status = useFormStatus();
  return (
    <button
      type="submit"
      disabled={status.pending}
      style={{ minWidth: "100px" }}
    >
      {label}
    </button>
  );
};

export default SubmitBtn;
```
        

 
🎉🎉[Project on GitHub]: React 19 Features (https://github.com/alaa-m1/react19-features)