Il est courant de vouloir persister un état entre les sessions. Le localStorage
est souvent utilisé, mais parfois les cookies sont plus adaptés : partage entre onglets, configuration serveur, ou SEO côté Next.js. Dans cet article, je propose un useCookie
🍪 maison , sans dépendance externe, capable de gérer les cookies trop longs grâce au chunking.
Pourquoi ne pas utiliser de librairie ? Tout simplement parce que tout se gère très bien de façon manuelle, cela permet également de mieux comprendre la mécanique des cookies et surtout on évite ainsi une dépendance supplémentaire dans le projet.
Les étapes
- Créer un hook custom qui va pouvoir gérer les cookies (synchronisation entre l'état en mémoire et les cookies du navigateur)
- Créer un helper qui va pouvoir lire et écrire les cookies dans le navigateur
- Implémenter le hook dans un composant
Création du hook custom
Le hook useCookies
retourne deux éléments :
- la valeur actuelle du cookie
- et la fonction qui va modifier le cookie
setValueAndCookie
.
Le hook s’appuie sur notre helper externe (détaillé plus bas) pour lire et écrire les cookies dans le navigateur.
Si le cookie n’existe pas encore, le hook l’initialise avec la valeur fournie (initialValue
). Sinon, il tente de lire et parser la valeur existante. On évite de parser inutilement si la valeur est une string
.
Pour plus de flexibilité, le hook accepte également un objet options (CookieOptions
) permettant de définir entre autres le path
, la durée (maxAge
) et le mode sécurisé (secure
).
L’initialisation se fait grâce à une fonction passée à useState, qui n’est exécutée qu’au premier rendu du composant. Cela permet de ne pas relire et parser le cookie à chaque re-render.
Comme on ne connaît pas le type de la valeur stockée, on utilise ici un type générique <T>
pour que le hook reste flexible et typé.
export const useCookies = <T,>(
key: string,
initialValue: T,
options?: CookieOptions
): [T, (value: T) => void] => {
const [value, setValue] = useState<T>(() => {
const storedCookie = cookieHelper.getItem(key);
const initialValueIsString = typeof initialValue === 'string';
if (storedCookie !== null) {
try {
return initialValueIsString ? (storedCookie as T) : (JSON.parse(storedCookie) as T);
} catch {
throw new SyntaxError(`Le cookie "${key}" est invalide ou mal formé.`);
}
}
const valueToStore = initialValueIsString ? String(initialValue) : JSON.stringify(initialValue);
cookieHelper.setItem(key, valueToStore, options);
return initialValue;
});
const setValueAndCookie = (newValue: T) => {
setValue(newValue);
const valueToStore = typeof newValue === 'string' ? newValue : JSON.stringify(newValue);
cookieHelper.setItem(key, valueToStore, options);
};
return [value, setValueAndCookie];
};
Création du helper
Pour centraliser et organiser la logique, on va créer un fichier à part dédié à la gestion des cookies.
Ce helper sera découpé en 4 parties :
- Préparer la valeur et ses options (durée de vie, sécurité, chemin, etc.)
- Lire un cookie existant (qu’il soit simple ou découpé en plusieurs morceaux, appelés chunks)
- Écrire un nouveau cookie (en un seul bloc ou en plusieurs chunks si sa taille dépasse la limite autorisée)
- Supprimer le cookie et tous ses éventuels morceaux
Ce helper est constitué de 3 méthodes exposées : getItem
, setItem
, removeItem
et de plusieurs fonctions utilitaires internes.
💡 Les chunks sont simplement des morceaux de données : on découpe un cookie trop long en plusieurs cookies plus petits pour respecter la limite de taille des navigateurs (environ 4 Ko par cookie).
Configuration et préparation
Les options des cookies sont des attributs envoyés au navigateur (path
, maxAge
, SameSite
, etc.). Ce sont des réglages supplémentaires et sont définis en fonction du rôle du cookie. Exemple: Un cookie qui doit être lu par un service externe → SameSite=None
+ Secure
.
const chunkSize = 3000;
const isProd = process.env.NODE_ENV === 'production';
export type SameSite = 'Strict' | 'Lax' | 'None';
export type CookieOptions = {
path?: string; //indiquer la limitation d'accès au cookie sur les pages utiles (par défaut: sur tout le site)
maxAge?: number; //indiquer la durée de vie en secondes
secure?: boolean; //indiquer "true" pour tout site sous https (ou la prod)
sameSite?: SameSite; //choisir la règle de transmission du cookie
domain?: string; //indiquer le domaine permettant l'accès aux cookies sur les sous-domaines liés
};
const splitIntoChunks = (value: string): string[] => {
const chunks: string[] = [];
for (let i = 0; i < value.length; i += chunkSize) {
chunks.push(value.slice(i, i + chunkSize));
}
return chunks;
};
const buildCookieOptions = (options?: CookieOptions): string => {
let attributesOption = `; path=${options?.path ?? '/'}`;
if (options?.maxAge !== undefined) {
attributesOption += `; max-age=${options.maxAge}`;
}
//active le cookie sécurisé en prod ou s'il est définit dans les options
if (isProd || options?.secure) {
attributesOption += '; Secure';
}
if (options?.sameSite) {
attributesOption += `; SameSite=${options.sameSite}`;
}
if (options?.domain) {
attributesOption += `; Domain=${options.domain}`;
}
return attributesOption;
};
const buildCookieString = (key: string, value: string, options?: CookieOptions): string => {
const keyAndValueCookie = `${key}=${encodeURIComponent(value)}`;
const cookieOptions = buildCookieOptions(options);
return keyAndValueCookie + cookieOptions;
};
Ici, on pose les bases :
-
chunkSize = 3000
→ un cookie ne peut pas dépasser environ 4 Ko, donc on découpe en morceaux si besoin (chunking). -
isProd
→ savoir si on est en mode production pour appliquer plus de sécurité. -
CookieOptions
→ un objet pour configurer chaque cookie (durée de vie, domaine, sécurité, etc.). -
splitIntoChunks
→ fonction utilitaire qui selon la taille de la valeur du cookie le découper en chunks. -
buildCookieOptions
→ configure les options du cookie en se basant sur ceux passés en paramètres. -
buildCookieString
→ cette fonction va construire un cookie (clé + valeur + option). On appelle la fonctionbuildCookieOptions
pour nous y aider.
Exemple en sortie :
const cookie = buildCookieString("theme", "dark", {
path: "/",
maxAge: 3600, // 1h
secure: true,
sameSite: "Lax",
domain: "exemple.com"
});
console.log(cookie);
theme=dark; path=/; max-age=3600; Secure; SameSite=Lax; Domain=exemple.com
Lire un cookie
const getCookieValue = (key: string): string | null => {
const cookies = document.cookie.split('; ');
const cookie = cookies.find((cookie) => cookie.startsWith(`${key}=`));
return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
};
getItem: (key: string): string | null => {
const cookie = getCookieValue(key);
if (cookie !== null && cookie !== '') {
return cookie;
}
const firstCookieChunked = getCookieValue(`${key}__0`);
if (firstCookieChunked !== null) {
let fullCookieValue = firstCookieChunked;
for (let i = 1; ; i++) {
const nextCookieValue = getCookieValue(`${key}__${i}`);
if (nextCookieValue === null) break;
fullCookieValue += nextCookieValue;
}
return fullCookieValue;
}
return null;
},
Ici, on lit un cookie depuis le navigateur :
-
getCookieValue
→ cherche un cookie dansdocument.cookie
grâce à sa clé et renvoie sa valeur décodée (ou null s’il n’existe pas). -
getItem
→ c'est notre méthode, elle appellegetCookieValue
qui va la traiter selon la taille du cookie :- Cas simple : si le cookie existe en un seul bloc, on le renvoie directement.
-
Cas chunké : si la donnée est découpée (
key__0
,key__1
, …), on recompose la valeur complète en concaténant tous les morceaux.
Si rien n’est trouvé, on renvoie null
.
💡 Bon à savoir : On décode la valeur avec decodeURIComponent
pour retrouver le texte original (les cookies sont toujours encodés dans le navigateur).
Supprimer un cookie
const deleteCookie = (key: string, options?: CookieOptions): void => {
const cookieOptions = {
...options,
maxAge: 0,
};
const expiredCookieOptions = buildCookieOptions(cookieOptions);
const expiredCookieString = `${key}=${encodeURIComponent('')}`;
document.cookie = expiredCookieString + expiredCookieOptions;
for (let i = 0; ; i++) {
const chunkKey = `${key}__${i}`;
if (!getCookieValue(chunkKey)) break;
document.cookie = `${chunkKey}=${encodeURIComponent('')}` + expiredCookieOptions;
}
};
removeItem: (key: string, options?: CookieOptions): void => {
deleteCookie(key, options);
},
Sur cette partie on supprime le cookie de la façon suivante grace à la méthode removeItem
:
- on reconstruit le cookie à l'identique (avec les mêmes options) et on vide sa valeur
- et si besoin on fait une repasse sur les chunks pour vider leurs valeurs aussi :
- Cas simple : on supprime le cookie unique.
-
Cas chunké : on récupère tous les cookies liés à une clé (chunks) et on les supprime tous (
key__0
,key__1
,.. ).
Modifier un cookie
setItem: (key: string, value: string, options?: CookieOptions): void => {
const chunks = splitIntoChunks(value);
const simpleCookie = chunks.length === 1;
deleteCookie(key, options);
if (simpleCookie) {
const singleChunk = chunks[0];
const cookieString = buildCookieString(key, singleChunk, options);
document.cookie = cookieString;
return;
}
chunks.forEach((chunk: string, i: number) => {
const chunkKey = `${key}__${i}`;
const cookieString = buildCookieString(chunkKey, chunk, options);
document.cookie = cookieString;
});
},
Et enfin pour modifier un cookie on crée la méthode setItem
qui va étape par étape :
- regrouper les chunks
- supprimer les anciens cookies associés à cette clé avant d’en créer de nouveaux
- modifier le cookie selon sa taille (chunk ou non)
Utilisation du hook dans un composant
'use client';
import React from 'react';
import { useCookies } from '@/hooks/useCookies';
export const ThemeToggle = () => {
const [theme, setTheme] = useCookies<'light' | 'dark'>('theme', 'light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<div style={{
backgroundColor: theme === 'dark' ? '#111' : '#f8f8f8',
color: theme === 'dark' ? '#fff' : '#000',
padding: '2rem',
textAlign: 'center',
minHeight: '100vh',
transition: 'all 0.3s ease'
}}>
<h1>Thème actuel : {theme}</h1>
<button onClick={toggleTheme}>
Changer de thème
</button>
</div>
);
};
Et Voilà, avec ce hook et ce helper, vous pouvez gérer les cookies en React sans dépendance externe, tout en restant flexible et typé.
👉 lien du gist
Top comments (0)