Auto save with a custom hook in Next.js 13

Sandipan Das
Sandipan Das · full-stack developer, Kolkata, IndiaPublished · Updated · 3 min read
Auto save with a custom hook in Next.js 13 — cover image

Auto save with a custom hook is about twenty lines: this one submits a form on a timer, so a long post saves itself while you write. I will show the whole thing, and then the three changes I make before it goes into production — my own editor has since moved on to a different approach, and I will explain why.

The hook does not know whether anything changed. It clicks submit on a schedule, dirty or not. That is the weakness to design around before you ship it.

Import useEffect from "react":

import { useEffect } from "react";

useEffect is the hook for side effects in a functional component. A timer is a side effect, so this is the right home for it.

Auto save with a custom hook: create useFormSubmitInterval

function useFormSubmitInterval(intervalMs: number): void {
  // ...
}

One argument, the interval in milliseconds. It returns nothing — it exists purely for the effect it sets up.

Inside the useEffect hook:

useEffect(() => {
  // ...
}, [intervalMs]);

The main logic lives here. It runs when the component mounts, and again if intervalMs changes.

Set up an interval to submit the form:

const formSubmitInterval = setInterval(() => {
  const submitButton = document.querySelector('button[type="submit"]');

setInterval runs the function repeatedly. Inside it, we look for a submit button in the page.

Check if a submit button was found:

if (submitButton) {
  // Simulate a click event on the submit button
  (submitButton as HTMLButtonElement).click();
}

If it is there, we click it — the same as the user pressing Save. Note the guard: without it, a page without that button throws on every tick.

Cleanup the interval on unmount:

return () => {
  clearInterval(formSubmitInterval);
};

Do not skip this. Without the cleanup the timer survives the component and keeps firing at a page that has gone, which is a memory leak and, worse, a save against the wrong record.

Dependency array [intervalMs]:

The effect re-runs whenever intervalMs changes, so the old timer is cleared and a new one started at the new duration.

Here is complete custom hook:

import { useEffect } from "react";


function useFormSubmitInterval(intervalMs: number): void {
  useEffect(() => {
    const formSubmitInterval = setInterval(() => {
      const submitButton = document.querySelector('button[type="submit"]');
      if (submitButton) {
        // Simulate a click event on the submit button
        (submitButton as HTMLButtonElement).click();
      }
      // You can add a condition here to handle the case when submitButton is null.
    }, intervalMs);

    return () => {
      clearInterval(formSubmitInterval);
    };
  }, [intervalMs]);
}
export default useFormSubmitInterval;

Call the hook where you have form with submit and want to use this custom hook

  // = = = = = = = = =  = = =
  // AUTO SAVE DRAFT
  // = = = = = = = = =  = = =
  const intervalMs = 5 * 60 * 1000; // 2 minutes
  useFormSubmitInterval(intervalMs);

What I would change before using this in production

Three things, in the order they will bite you:

  • Correct the comment. 5 * 60 * 1000 is five minutes, not two. Small thing, but it is the kind of mismatch someone trusts later.
  • It saves whether or not you changed anything. Every tick writes a record and bumps the updated date. Gate it on the form being dirty.
  • querySelector is fragile. It grabs the first submit button in the whole document — not necessarily yours. Calling the form's own submit handler is steadier than pretending to be a user.

My own editor now does it differently. It snapshots the draft to the browser on a timer and offers it back if the tab dies, and the real save stays a deliberate press. That distinction matters more than the timer does.

Questions

Questions this post answers

How does the auto save custom hook know when to submit?

It does not know anything about your form. It runs a timer with setInterval, looks for a submit button in the page, and clicks it — the same as the user pressing Save. The guard around that lookup matters: without it, a page without that button throws on every tick.

Does the hook save only when something has changed?

No, and this is its main weakness. It clicks submit on a schedule, dirty or not, so every tick writes a record and bumps the updated date. Gate it on the form being dirty before you ship it.

Why does the interval need a cleanup?

Without the cleanup the timer survives the component and keeps firing at a page that has gone. That is a memory leak and, worse, a save against the wrong record.

Would you use this approach in production today?

My own editor no longer does it this way. It snapshots the draft to the browser on a timer and offers it back if the tab dies, and the real save stays a deliberate press. That distinction matters more than the timer does.

Share:X·LinkedIn·Email

Related

  • SEO for Developers

    SEO vs AEO vs GEO

    SEO vs AEO vs GEO in one paragraph SEO vs AEO vs GEO: SEO makes a page findable and worth reading. AEO adds one rule — the answer first, standing alone, then the proof — so an answer engine can…

    · 6 min read · by Sandipan Das
  • SEO for Developers

    SEO vs AEO

    SEO vs AEO in one paragraph SEO vs AEO comes down to this: SEO — search engine optimisation — is the work that makes a page findable and worth reading: indexed, fast, honest, linked to. AEO — answer engine optimisation…

    · 6 min read · by Sandipan Das
  • SEO for Developers

    What is AI SEO?

    What is AI SEO, and what it isn't What is AI SEO? AI SEO means one of two things: using AI models to do SEO work faster — reading an audit into a fix list, grouping thousands of keywords into…

    · 7 min read · by Sandipan Das

If you'd rather not do it yourself

Everything above works. You just need the time for it.

Follow the steps and fix it yourself. Or send the site to the team and get on with your day.

900+ projects completed since 2017, from Kolkata, India.