Thinking In Web State With React Router 7
Remix is Dead, Long Live Remix!
Persistence As State
We can also persist state using cookies and localStorage. LocalStorage works the same as it would in React app, so we will skip over it. React Router has its own factory function for handling cookies that we will be using createCookieSessionStorage():
const { getSession, commitSession, destroySession } = createCookieSessionStorage({
    cookie: {
        name: "__session",
        secrets: process.env.SECRET,
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        sameSite: "lax",
        path: "/",
    },
});Now we can set and read our cookie via the loader and action, and then pass data down to our component via useLoaderData() as we have done before.
type Level = "child" | "adult" | "expert";
export async function loader({ request }: LoaderFunctionArgs) {
    const {session, keyValue} = await getKeyValue(request);
    const level = (keyValue["read:value"] as Level | undefined) ?? "adult";
    return Response.json({ level }, {
        headers: { "Set-Cookie": await commitSession(session) }
    });
}export async function action({ request }: ActionFunctionArgs) {
    const session = await getSession(request.headers.get("Cookie"));
    const fd = await request.formData();
    const key = String(fd.get("key"));
    const value = String(fd.get("value")) ?? "adult";
    setKeyValue(session, { [key]: value });
    return new Response(null, {
        status: 204,
        headers: { "Set-Cookie": await commitSession(session)}
    });
}export default ReadingLevels() {
    const {level} = useLoaderData<typeof loader>();
    return(
        <div>
            <h1>Reading Level</h1>
            <p>Select your preferred reading level.</p>
            <Form method="post">
                <input type="hidden" name="key" value="read:level" />
                <label htmlFor="lvl">Reading level:</label>
                <div>
                    <button type="submit" name="value" value="child">
                        <div>Child</div>
                        <div>Simple words & short sentences</div>
                    </button>
                    
                    <button type="submit" name="value" value="adult">
                        <div>Adult</div>
                        <div>Standard prose with key terms</div>
                    </button>
                    <button type="submit" name="value" value="expert">
                        <div>Expert</div>
                        <div>Technical terms & precise phrasing</div>
                    </button>
                </div>
            </Form>
            <div>
                ....
            <div>
        </div>
    );
}And here's our working example, which shows 3 topics in three different reading levels.
Reading Level
Select your preferred reading level.
Physics: Conservation of Energy
Height trades for speed; total mechanical energy is ~constant.
Ignoring losses, m·g·h + ½·m·v² stays constant along the track.
Rolling resistance and drag convert some mechanical energy into heat/sound, requiring external work to start or maintain motion.
Biology: Photosynthesis
Light reactions power carbon fixation in chloroplasts.
Photons drive electron transport to generate ATP and NADPH; water splitting releases O₂.
The Calvin cycle fixes CO₂ via Rubisco, producing carbohydrates from triose phosphates.
History: France 1789–1799
Fiscal crisis sparked reform; ideals met turmoil and war.
Insolvency and grain shortages catalyzed 1789 and the Declaration of the Rights of Man.
Radicalization led to the Terror, then stabilization and Napoleon’s rise.
And that's it, just some of the ways you can handle state in React Router. All three of these methods builds on web fundamentals without having to resort to React hooks for everything. Hopefully you found this useful, or at the very least interesting enough to get you to explore React Router further.