Clear Cookie when navigating away in Remix

I use a cookie to share state between loader and action in a page in Remix (user makes “local” state changes before clicking “submit” and saving to the db). I need to reset this cookie either when the user leaves the page, or when the user navigates to the page.

Because all the behavior on the page is taking place with loaders and actions, every click the user makes runs an action and then the loader. So we can’t just reset the cookie in the loader — unless we can only do it when the loader is running because of a navigation.

I’ve tried writing the cookie-clearing behavior into my action (formData.intent === 'reset') but I need some way to call that action.

useBeforeUnload doesn’t work, and submitting the reset action from a useEffect return doesn’t work because, since it’s Remix forms, the component unmounts every time any action is run.

(I don’t think any code is needed to communicate this problem. If you want to ask me to include some code, please make sure you’ve read my question and be specific about what part is unclear)

I hope I’m understand what you’re asking.

I created an example that creates a local-state cookie. I include the current path as a value, so if you navigate to a new route, it will automatically clear the data.

https://stackblitz.com/edit/remix-run-remix-gvgcvj?file=app%2Froutes%2Ftest-a.tsx

If this isn’t what you’re asking for, please provide some clarifications. Thanks!

// local-state.server.ts
import * as Cookie from 'cookie';

export function getLocalState(request: Request) {
  let url = new URL(request.url);
  let cookies = Cookie.parse(request.headers.get('cookie') ?? '');
  let localStateCookie = JSON.parse(cookies['local-state'] ?? '{}');
  if (localStateCookie.path !== url.pathname) {
    // this cookie no longer valid, so clear it out
    localStateCookie = { path: url.pathname };
  }
  return localStateCookie;
}

export function commitLocalState(localStateCookie: any) {
  return Cookie.serialize('local-state', JSON.stringify(localStateCookie));
}

Here’s how you use it.

// routes/test-a.tsx
export async function loader({ request }: DataFunctionArgs) {
  let localState = await getLocalState(request);
  localState.message="hello from route test-A";
  localState.now = new Date().toLocaleString();

  return json(
    { from: 'test-a', localState },
    { headers: [['set-cookie', await commitLocalState(localState)]] }
  );
}

export async function action({ request }: DataFunctionArgs) {
  let localState = await getLocalState(request);
  let formData = await request.formData();

  localState.now = new Date().toLocaleString();
  let data = Object.fromEntries(formData.entries());
  // need to use formData.getAll since it's a multi-valued form field
  // Object.fromEntries() will only get the last item
  data['favorite-fruit'] = formData.getAll('favorite-fruit');
  localState = { ...localState, ...data };

  throw redirect('/test-a', {
    headers: [['set-cookie', await commitLocalState(localState)]],
  });
}

Leave a Comment