Skip to main content
Version: 4.xx.xx
Source Code

<AutoSaveIndicator />

Refine's forms provide a built-in auto-save feature. This allows you to automatically save the form when the user makes changes to the form which can be useful for forms that are long or complex and the user may not want to lose their progress.

The <AutoSaveIndicator /> component is a utility component that can be used to show a visual indicator to the user about the auto-save status of the form.

Good to know:
  • Refine's core useForm hook does not automatically trigger the auto-save feature. You need to manually trigger the onFinishAutoSave function returned from the useForm hook to trigger the auto-save feature.

  • Extended implementations of Refine's useForm such as; @refinedev/antd's useForm, @refinedev/react-hook-form's useForm and @refinedev/mantine's useForm automatically trigger the auto-save feature when a form value changes.

  • The <AutoSaveIndicator /> component is only designed to display a visual feedback to the user about the auto-save status of the form. It does not contain any logic to trigger the auto-save feature.

  • To learn more about the auto-save feature check out Auto Save section in Forms guide

Usage

Usage is as simple as spreading the autoSaveProps object returned from the useForm hook into the <AutoSaveIndicator /> component. It will automatically determine the auto-save status and display the appropriate indicator.

import { AutoSaveIndicator, useForm } from "@refinedev/core";

const EditPage = () => {
const { autoSaveProps } = useForm({
autoSave: {
enabled: true,
},
});

console.log(autoSaveProps);
/*
{
status: "success", // "loading" | "error" | "idle" | "success"
error: null, // HttpError | null
data: { ... }, // UpdateResponse | undefined,
}
*/

return (
<div>
{/* We'll pass the autoSaveProps from useForm's response to the <AutoSaveIndicator /> component. */}
<AutoSaveIndicator {...autoSaveProps} />
<form
// ...
>
{/* ... */}
</form>
</div>
);
};

Example below shows the <AutoSaveIndicator /> component in action.

import React from "react";
import { useForm, useSelect, AutoSaveIndicator, HttpError, BaseKey } from "@refinedev/core";

export const Edit: React.FC = () => {
  const { query, isLoading, onFinish, autoSaveProps, onFinishAutoSave } = useForm<
    IProduct,
    HttpError,
    FormValues
  >({
    autoSave: {
      enabled: true,
      interval: 1000,
    },
  });

  const { options: categorySelectOptions } = useSelect({
    resource: "categories",
  });

  const defaultValues = query?.data?.data;

  return (
    <div className="page">
      <div className="auto-save-wrapper">
        <AutoSaveIndicator {...autoSaveProps} />
      </div>
      <form
        onChange={(event) => {
          const formData = new FormData(event.currentTarget);

          onFinishAutoSave(transformValues(Object.fromEntries(formData.entries()) as RawFormValues));
        }}
        onSubmit={(event) => {
          event.preventDefault();
          const formData = new FormData(event.currentTarget);

          onFinish(transformValues(Object.fromEntries(formData.entries()) as RawFormValues));
        }}
      >
        <label htmlFor="name">
          <span>Name</span>
          <input name="name" placeholder="Name" defaultValue={defaultValues?.name} />
        </label>
        <label htmlFor="description">
          <span>Description</span>
          <textarea name="description" placeholder="Description" defaultValue={defaultValues?.description} />
        </label>
        <label htmlFor="material">
          <span>Material</span>
          <input name="material" placeholder="Material" defaultValue={defaultValues?.material} />
        </label>
        <label htmlFor="category">
          <span>Category</span>
          <select name="category" defaultValue={defaultValues?.category?.id}>
            {categorySelectOptions.map((option) => (
              <option key={option.value} value={option.value}>
                {option.label}
              </option>
            ))}
          </select>
        </label>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
};

const transformValues = (values: RawFormValues): FormValues => {
  return {
    ...values,
    category: values.category ? { id: values.category } : undefined,
  };
};

interface IProduct {
  id: BaseKey;
  name: string;
  material: string;
  description: string;
  category: { id: BaseKey; name: string };
}

interface FormValues {
  name?: string;
  material?: string;
  description?: string;
  category?: { id: BaseKey };
}

interface RawFormValues extends FormValues {
  category?: BaseKey;
}

Customizing the indicator

The <AutoSaveIndicator /> component accepts an elements prop which can be used to customize the indicator for each status.

import { AutoSaveIndicator, useForm } from "@refinedev/core";

const EditPage = () => {
const { autoSaveProps } = useForm({
autoSave: {
enabled: true,
},
});

return (
<div>
<AutoSaveIndicator
{...autoSaveProps}
elements={{
loading: <span>saving...</span>,
error: <span>auto save error.</span>,
idle: <span>waiting for changes.</span>,
success: <span>saved.</span>,
}}
/>
{/* ... */}
</div>
);
};

API Reference

Properties

PropertyTypeDescription
data

UpdateResponse<BaseRecord>

The data returned by the update request.

error

HttpError | null

The error returned by the update request.

status

"loading" | "error" | "idle" | "success"

The status of the update request.

elements

Partial<Record<"loading" | "error" | "idle" | "success", ReactNode>>

The elements to display for each status.