Gathering detailed insights and metrics for next-isomorphic-cookies
Gathering detailed insights and metrics for next-isomorphic-cookies
Using cookies in NextJS made easy! Seamless integration with SSG and SSR, while avoiding hydration mismatches.
npm install next-isomorphic-cookies
Typescript
Module System
Node Version
NPM Version
36.4
Supply Chain
79.5
Quality
75.3
Maintenance
100
Vulnerability
92.3
License
TypeScript (98.8%)
JavaScript (1.2%)
Total Downloads
9,054
Last Day
52
Last Week
243
Last Month
952
Last Year
6,063
4 Stars
23 Commits
2 Watching
2 Branches
1 Contributors
Minified
Minified + Gzipped
Latest Version
1.0.0-alpha.7
Package Id
next-isomorphic-cookies@1.0.0-alpha.7
Unpacked Size
44.25 kB
Size
11.12 kB
File Count
34
NPM Version
8.11.0
Node Version
16.16.0
Publised On
13 Jan 2023
Cumulative downloads
Total Downloads
Last day
-13.3%
52
Compared to previous day
Last week
-8.3%
243
Compared to previous week
Last month
27.8%
952
Compared to previous month
Last year
163.4%
6,063
Compared to previous year
1
Using cookies in NextJS made easy!
Seamless integration with SSG and SSR, while avoiding hydration mismatches.
App
at _app
:1// _app 2 3import type { AppProps } from "next/app"; 4import { withCookiesAppWrapper } from "next-isomorphic-cookies"; 5 6const MyApp = ({ Component, pageProps }: AppProps) => { 7 return <Component {...pageProps} />; 8}; 9 10// Wrapper! \/ 11export default withCookiesAppWrapper(MyApp); // <------ Wrap!
getServerSideProp
(when using SSR):1const Page = () => { 2 //... 3}; 4 5export const getServerSideProps: GetServerSideProps = 6 // Wrapper! \/ 7 withCookiesGetServerSidePropsWrapper(async (context) => { 8 // Your usual getServerSideProps 9 //... 10 }); 11 12export default Page;
useCookieState
:1import { useCookieState } from "next-isomorphic-cookies"; 2 3const FoodTypeCookieKey = "FoodType"; 4 5const foodTypes = ["Japanese", "Mexican", "Italian"]; 6 7export const FoodTypeFilter = () => { 8 // Initializer that allows us to deal with the case 9 // when there is no food type stored or, 10 // when the stored food type is not available anymore 11 const foodTypeInitializer = (storedFoodType: string) => { 12 const defaultFoodType = foodTypes[0]; 13 14 const cookieNotSet = storedFoodType === undefined; 15 if (cookieNotSet) { 16 return defaultFoodType; 17 } 18 19 const storedFoodTypeIsNotAvaialble = !foodTypes.includes(storedFoodType); 20 if (storedFoodTypeIsNotAvaialble) { 21 return defaultFoodType; 22 } 23 24 return storedFoodType; 25 }; 26 27 // When using SSR, the initializer receives 28 // the stored value in the very first render. 29 // When using SSG, the initializer receives 30 // undefined in the first render, and then 31 // calls the initializer again with the stored 32 // value immediately after the first render. 33 const { value: foodType, setValue: setFoodType } = useCookieState<string>( 34 FoodTypeCookieKey, 35 foodTypeInitializer 36 ); 37 38 return ( 39 <div> 40 <label>Food Type</label>{" "} 41 <select 42 value={value} 43 onChange={(event) => { 44 // Automatically persists value 45 // in the cookie 46 setFoodType(event.target.value); 47 }} 48 > 49 <option value="Japanese">Japanese</option> 50 <option value="Mexican">Mexican</option> 51 <option value="Italian">Italian</option> 52 </select> 53 </div> 54 ); 55};
You must wrap your App at _app
with this function.
Example:
1// _app 2 3import type { AppProps } from "next/app"; 4import { withCookiesAppWrapper } from "next-isomorphic-cookies"; 5 6const MyApp = ({ Component, pageProps }: AppProps) => { 7 return <Component {...pageProps} />; 8}; 9 10// Wrapper! \/ 11export default withCookiesAppWrapper(MyApp); // <------ Wrap!
Wraps getServerSideProps
to make cookies available to components rendering in the server.
If you do not wrap getServerSideProps
with this function, either because you're using SSG, or because you simply forgot, the only thing that will happen is that the state that relies on cookies to be initialized will be synced with cookies after the first render, but it won't break anything.
Example:
1const Page = () => { 2 //... 3}; 4 5export const getServerSideProps: GetServerSideProps = 6 // Wrapper! \/ 7 withCookiesGetServerSidePropsWrapper(async (context) => { 8 // Your usual getServerSideProps 9 //... 10 }); 11 12export default Page;
Can be called inside any component, as long as you've wrapped your App with withCookiesAppWrapper
.
1export type UseCookieState<T> = ( 2 /** 3 * Cookie key/name. 4 */ 5 key: string, 6 7 /** 8 * Function that is called to initialize 9 * the state value and, in cases where 10 * cookies are not available on the server, 11 * again after hydration. 12 */ 13 initializer: (storedValue: T | undefined) => T, 14 15 options?: UseCookieStateOptions<T> 16) => { 17 /** 18 * Pretty much like the value you'd get 19 * when calling `useState`. 20 */ 21 value: T; 22 23 /** 24 * Pretty much like the setter you'd get 25 * when calling `useState`. 26 * 27 * When `storeOnSet` option is enabled, 28 * everytime this is called, it also 29 * stores the value it was called with 30 * in the cookie. 31 */ 32 setValue: Dispatch<SetStateAction<T>>; 33 34 /** 35 * Reads value off cookie and calls sets 36 * `value` to it. 37 * 38 * You may optionally pass a `deserializer` 39 * that transforms the cookie value before 40 * setting value to it. 41 */ 42 retrieve: (options?: { 43 /** 44 * Transforms the cookie value before 45 * setting value to it. 46 * 47 * Defaults to identity function. 48 */ 49 deserializer?: (storedValue: T | undefined) => T; 50 }) => void; 51 52 /** 53 * Stores value in cookie. 54 */ 55 store: ( 56 /** 57 * Value to be stored. 58 */ 59 value: T, 60 61 options?: { 62 /** 63 * js-cookie attributes 64 */ 65 attributes?: CookieAttributes; 66 /** 67 * Transforms the value before 68 * it is stored. 69 * 70 * Defaults to identity function. 71 */ 72 serializer?: (storedValue: T | undefined) => T; 73 } 74 ) => void; 75 76 /** 77 * Clears cookie value. 78 */ 79 clear: () => void; 80 81 /** 82 * If cookies are not available during hydration, 83 * state will have to be synchronized after hydration, 84 * in which case this flag will be true until synchronization 85 * is finished. 86 */ 87 isSyncing: boolean; 88}; 89 90type UseCookieStateOptions<T> = { 91 /** 92 * Defaults to true. 93 * 94 * Whether the value should be stored 95 * in the cookie everytime setValue 96 * is called. 97 * 98 * It is possible to pass a serializer 99 * that will transform the value 100 * before it is stored as a cookie. 101 */ 102 storeOnSet?: StoreOnSetOption<T>; 103}; 104 105type StoreOnSetOption<T> = 106 | true 107 | false 108 | { 109 /** 110 * js-cookie attributes 111 */ 112 attributes?: CookieAttributes; 113 114 /** 115 * Transforms the value before 116 * it is stored. 117 * 118 * Defaults to identity function. 119 */ 120 serializer?: (value: T) => T; 121 }; 122 123// Attributes from js-cookie 124interface CookieAttributes { 125 /** 126 * Define when the cookie will be removed. Value can be a Number 127 * which will be interpreted as days from time of creation or a 128 * Date instance. If omitted, the cookie becomes a session cookie. 129 */ 130 expires?: number | Date | undefined; 131 132 /** 133 * Define the path where the cookie is available. Defaults to '/' 134 */ 135 path?: string | undefined; 136 137 /** 138 * Define the domain where the cookie is available. Defaults to 139 * the domain of the page where the cookie was created. 140 */ 141 domain?: string | undefined; 142 143 /** 144 * A Boolean indicating if the cookie transmission requires a 145 * secure protocol (https). Defaults to false. 146 */ 147 secure?: boolean | undefined; 148 149 /** 150 * Asserts that a cookie must not be sent with cross-origin requests, 151 * providing some protection against cross-site request forgery 152 * attacks (CSRF) 153 */ 154 sameSite?: "strict" | "Strict" | "lax" | "Lax" | "none" | "None" | undefined; 155}
A "lower-level" hook, that can be used in case you want to manage state yourself.
1export type UseCookie = <T>(key: string) => { 2 /** 3 * Retrieves cookie value. 4 * 5 * In the server **and during hydration**, ALWAYS 6 * returns the server cookie value, to prevent 7 * hydration mismatches. 8 * 9 * After hydration, returns the client cookie value. 10 */ 11 retrieve: () => T | undefined; 12 13 /** 14 * Stores value in cookie. 15 */ 16 store: (data: T, attributes?: CookieAttributes) => void; 17 18 /** 19 * Clear cookie value. 20 */ 21 clear: (attributes?: CookieAttributes) => void; 22 23 /** 24 * True whenever there are no cookies in the server, either because 25 * we're using SSG, or because we didn't wrap `getServerSideProps` 26 * with `withCookiesGetServerSideWrapper` AND it is hydrating. 27 * 28 * This indicates that we need to synchronize React state 29 * with client side cookie value after hydration. 30 */ 31 needsSync: boolean; 32};
Example:
1const { retrieve, needsSync } = useCookie("SomeCookie"); 2const [value, setValue] = useState(needsSync ?? retrieve()); 3 4useSyncWithCookie((storedValue) => { 5 setValue(storedValue); 6});
TODO
To be used in conjunction with useCookie
to deal with state synchronization after hydration.
1type UseSyncWithCookie = <T>(key: string, sync: (cookieValue: T | undefined) => void)
TODO
When using NextJS (or any kind of server rendering), our React components end up getting rendered in two very different environments: browser and server.
So, if you have a React component that reads values from cookies while rendering, while it works fine when rendering in the browser, it'll crash your application when rendering in the server:
1const getCookie = (key: string) => { 2 // Toy implementation for the sake of the argument, 3 // most likely misses several edge cases 4 const value = `; ${document.cookie}`; 5 const parts = value.split(`; ${name}=`); 6 if (parts.length === 2) { 7 return JSON.parse(parts.pop().split(";").shift()); 8 } 9}; 10 11const FoodTypeCookieKey = "FoodType"; 12 13const foodTypes = ["Japanese", "Mexican", "Italian"]; 14 15export const FoodTypeFilter = () => { 16 // This call to `getCookie` will crash the application, 17 // because there is no `document` in the server 18 const [foodType, setFoodType] = useState(getCookie(FoodTypeCookieKey)); 19 20 return ( 21 <div> 22 <label>Food Type</label>{" "} 23 <select 24 value={value} 25 onChange={(event) => { 26 setFoodType(event.target.value); 27 }} 28 > 29 <option value="Japanese">Japanese</option> 30 <option value="Mexican">Mexican</option> 31 <option value="Italian">Italian</option> 32 </select> 33 </div> 34 ); 35};
And then probably, you might try something like this:
1const FoodTypeCookieKey = "FoodType"; 2 3const foodTypes = ["Japanese", "Mexican", "Italian"]; 4 5const isServer = typeof document === "undefined"; 6 7export const FoodTypeFilter = () => { 8 // In the server we default to a fallback value, 9 // so getCookie only gets called in the client 10 const initialFoodType = isServer 11 ? foodTypes[0] 12 : getCookie(FoodTypeCookieKey); 13 const [foodType, setFoodType] = useState(initialFoodType); 14 15 return ( 16 <div> 17 <label>Food Type</label>{" "} 18 <select 19 value={value} 20 onChange={(event) => { 21 setFoodType(event.target.value); 22 }} 23 > 24 <option value="Japanese">Japanese</option> 25 <option value="Mexican">Mexican</option> 26 <option value="Italian">Italian</option> 27 </select> 28 </div> 29 ); 30};
But then you're gonna get a hydration mismatch, because the HTML that was generated in the server must match exactly the HTML that is generated on the client in the first render.
Finally, you remember that useEffect
only runs in the client, and that runs after the render phase:
1const FoodTypeCookieKey = "FoodType"; 2 3const foodTypes = ["Japanese", "Mexican", "Italian"]; 4 5const isServer = typeof document === "undefined"; 6 7export const FoodTypeFilter = () => { 8 const [foodType, setFoodType] = useState(foodTypes[0]); 9 10 useEffect(() => { 11 // Now we're safe because useEffect 12 // runs **after** the render phase, 13 // so the very first render will generate 14 // exactly the same HTML that was sent by the server, 15 // and then after the first render, we update 16 // the value with the cookie value 17 setFoodType(getCookie(FoodTypeCookieKey)); 18 }, []); 19 20 return ( 21 <div> 22 <label>Food Type</label>{" "} 23 <select 24 value={value} 25 onChange={(event) => { 26 setFoodType(event.target.value); 27 }} 28 > 29 <option value="Japanese">Japanese</option> 30 <option value="Mexican">Mexican</option> 31 <option value="Italian">Italian</option> 32 </select> 33 </div> 34 ); 35};
After a little bit more of head-scratching, you're probably gonna think:
But, wait a moment, if I use SSR, won't I have access to the request and thus, to the cookies? So why can't I use the cookie value while rendering on the server?
And you're right, you absolutely can, the only nuisance is that the only place you have access to cookies is inside getServerSideProps
, so you have to pass the cookie value all the way down to your component, or use the Context API:
1type PageProps = { 2 cookies: Record<string, string>; 3}; 4 5const Page = ({ cookies }: PageProps) => { 6 //... 7 return ( 8 <CookiesContext.Provider value={cookies}> 9 {/* ... */} 10 </CookiesContext.Provider> 11 ); 12}; 13 14export const getServerSideProps = async (context) => { 15 // ... 16 return { 17 props: { 18 cookies: context.req.cookies, 19 }, 20 }; 21};
1const FoodTypeCookieKey = "FoodType"; 2 3const foodTypes = ["Japanese", "Mexican", "Italian"]; 4 5const isServer = typeof document === "undefined"; 6 7export const FoodTypeFilter = () => { 8 const cookies = useContext(CookiesContext); 9 const [foodType, setFoodType] = useState( 10 JSON.parse(cookies[FoodTypeCookieKey]) 11 ); 12 13 return ( 14 <div> 15 <label>Food Type</label>{" "} 16 <select 17 value={value} 18 onChange={(event) => { 19 setFoodType(event.target.value); 20 }} 21 > 22 <option value="Japanese">Japanese</option> 23 <option value="Mexican">Mexican</option> 24 <option value="Italian">Italian</option> 25 </select> 26 </div> 27 ); 28};
However, now your FoodTypeFilter
component cannot be used in pages that use SSG anymore, because we do not have access to the request in SSG (after all, it renders the page in build time), so cookies
will be undefined, which will crash your application.
And we could go on, but the point is, with next-isomorphic-cookies
you can absolutely forget about all of these issues, you don't even need to know whether your component is going to be used in a page that uses SSR, or SSG, because we take care of everything for you.
TODO
TODO
No vulnerabilities found.
No security vulnerabilities found.