Persist form inputs across page reloads
-- 2 min read
It's annoying when a page refreshes when you were filling in a form; you lose all your data and now you have to start all over 😐.
Persisting the values in form inputs as a user is filling in a form creates a great user experience. In this article, we will learn how to persist values in form inputs using cookies. If you're unfamiliar with cookies, learn more about them here.
Project setup
In this project I will be using Remix framework, but you're free to use whichever framework you're comfortable with; the fundamentals are the same. Let's start by initializing a Remix application:
npx create-remix@latest
Remix has great apis for handling sessions and cookies. We will use the built-in utility createCookieSessionStorage() to create a session based cookie. Let's create a file session.js and initialize the session storage object.
// app/.server/session.js
import { createCookieSessionStorage } from "@remix-run/node"; // or cloudflare/deno
export let { getSession, commitSession, destroySession } =
createCookieSessionStorage({
// a Cookie from `createCookie` or the same CookieOptions to create one
cookie: {
name: "checkout__session",
secrets: ["s3cr345"],
sameSite: "lax",
path: '/',
maxAge: 60 * 60 * 24,
httpOnly: true,
secure: true,
},
});
The function createCookieSessionStorage() returns a session storage object which has three methods ( getSession(), commitSession(), and destroySession() ) that will help us interact with cookies and the data in the cookies.
Add user input to the cookie
Now that we have a session storage object, let's use it to add the user data to the cookie. We want to save changes as the user is using the form, therefore we have to submit the input values to the server and save them in a session cookie whenever an input receives focus then loses it.
We do this in Remix by submitting the form in an input's onBlur event handler using the useSubmit() hook.
// app/routes/checkout.jsx
import { json, useLoaderData, useSubmit } from "@remix-run/react";
import { getSession, commitSession } from "~/.server/session";
export async function action({ request }) {
let session = await getSession(request.headers.get('Cookie'));
let formData = await request.formData();
let name = formData.get('name');
let email = formData.get('email');
let userData = { name, email };
// Put the user details in the session
session.set('userData', userData);
return json({ ok: true }, {
headers: {
"Set-Cookie": await commitSession(session)
}
});
}
export default function Checkout() {
let submit = useSubmit();
return (
<main>
<hi>Checkout</h1>
<form method="post">
<fieldset>
<label>
Name
<input
type="text"
name="name"
// Submit the form when the user leaves the input
onBlur={(event) => { submit(event.currentTarget.form )}}
/>
</label>
<label>
Email
<input
type="email"
name="email"
// Submit the form when the user leaves the input
onBlur={(event) => { submit(event.currentTarget.form )}}
/>
</label>
<button type="submit">Check out</button>
</fieldset>
</form>
</main>
);
}
Use cookie values as default inputs
Finally we use the input values from the cookie as the default values for the input fields. We get the values from the loader function and pass them to the component to be used.
// app/routes/checkout.jsx
import { useLoaderData } from "@remix-run/react";
import { getSession } from "~/.server/session";
export async function loader({ request }) {
let session = await getSession(request.headers.get('Cookie'));
// Retrieve the user data that was set in the session
let userData = session.get('userData') ?? null;
return userData;
}
export default function Checkout() {
// Get the user data from the loader
let userData = useLoaderData();
return (
<main>
<h1>Checkout</h1>
<form method="post">
<fieldset>
<label>
Name
<input
type="text"
name="name"
// Use the value from the cookie as the default value
defaultValue={userData?.name}
/>
</label>
<label>
Email
<input
type="email"
name="email"
// Use the value from the cookie as the default value
defaultValue={userData?.email}
/>
</label>
<button type="submit">Check out</button>
</fieldset>
</form>
</main>
);
}
This way we prevent unnecessary loss of user input data, leading to a more satisfying user experience.
Final outcome
This is how the experience looks like after implementing the tricks that we've learnt today.