Use cookies in a server rendered React application for managing user preferences
When a user visits your application, you may want to remember their preferences, such as theme or language settings. Using cookies allows you to persist these preferences across sessions.
root.tsx
You need to expose the cookie data at the root of the app so that nested components can access it.
export async function loader({ request }: Route.LoaderArgs) {
// read cookie header from the request and return it as part of the loader data
// so that nested components can access the cookie data.
const cookie = request.headers.get('cookie');
return data({
cookie,
});
}hooks/useCookies.ts
Create a reusable hook for accessing and managing cookies in your React components. This makes it easy to read and update cookie values without directly interacting with the universal-cookie API.
It's meant to mimic the useState hook, providing a similar API for managing cookie values in a React-friendly way.
import { useEffect, useMemo, useState } from 'react';
import { useRouteLoaderData } from 'react-router';
import Cookies, {
type CookieChangeOptions,
type CookieSetOptions,
} from 'universal-cookie';
import type { loader } from '~/root';
type UseCookieOptions<T> = {
defaultValue: T;
};
export default function useCookies() {
const cookieHeader = useRouteLoaderData<typeof loader>('root')?.cookie;
const cookies = useMemo(
() => new Cookies(cookieHeader, { path: '/' }),
[cookieHeader],
);
return cookies;
}
export function useCookie<T>(name: string, options: UseCookieOptions<T>) {
const cookies = useCookies();
const defaultValue = options.defaultValue;
const [cookieValue, setCookieValueInternal] = useState<T>(
() => cookies.get<T>(name) ?? defaultValue,
);
function setCookieValue(
value: T | ((prev: T) => T),
options?: CookieSetOptions,
) {
const newValue =
typeof value === 'function'
? (value as (prev: T) => T)(cookieValue)
: value;
cookies.set(name, newValue, options);
setCookieValueInternal(newValue);
}
useEffect(() => {
function handleCookieChange(options: CookieChangeOptions) {
if (options.name === name) {
setCookieValueInternal(options.value);
}
}
cookies.addChangeListener(handleCookieChange);
return () => {
cookies.removeChangeListener(handleCookieChange);
};
}, [cookies, name]);
return [cookieValue, setCookieValue] as const;
}Usage
import { useMediaQuery } from 'usehooks-ts';
function MyComponent() {
/** Use Cookie hook to get/set the user's preferred theme from cookies. */
const [preferredTheme, setPreferredTheme] = useCookie<
'light' | 'dark' | undefined
>('theme', { defaultValue: undefined });
/**
* Detect OS theme preference. This will only run on the client side since it
* uses a media query to check the user's system settings. In SSR, it will
* default to false (light mode).
*/
const isDarkOS = useMediaQuery('(prefers-color-scheme: dark)', {
initializeWithValue: false,
defaultValue: false,
});
/**
* Determine current theme based on whether the user has a preference set or
* fall back to OS preference
*/
const themeName = useMemo(
() => preferredTheme || (isDarkOS ? 'dark' : 'light'),
[preferredTheme, isDarkOS],
);
const toggleTheme = useCallback(() => {
if (themeName === 'dark') {
setPreferredTheme('light');
} else {
setPreferredTheme('dark');
}
}, [themeName, setPreferredTheme]);
const [theme, setTheme] = useCookie('theme', { defaultValue: 'light' });
return (
<div>
<p>Preferred theme: {String(preferredTheme)}</p>
<p>OS prefers dark mode: {String(isDarkOS)}</p>
<p>Current theme: {themeName}</p>
<button onClick={toggleTheme}>
Toggle Theme
</button>
</div>
);
}
Last updated on