diff --git a/docs/useCookie.md b/docs/useCookie.md new file mode 100644 index 00000000..92bf9f63 --- /dev/null +++ b/docs/useCookie.md @@ -0,0 +1,39 @@ +# `useCookie` + +React hook that returns the current value of a `cookie`, a callback to update the `cookie` +and a callback to delete the `cookie.` + +## Usage + +```jsx +import { useCookie } from "react-use"; + +const Demo = () => { + const [value, updateCookie, deleteCookie] = useCookie("my-cookie"); + const [counter, setCounter] = useState(1); + + useEffect(() => { + deleteCookie(); + }, []); + + const updateCookieHandler = () => { + updateCookie(`my-awesome-cookie-${counter}`); + setCounter(c => c + 1); + }; + + return ( +
+

Value: {value}

+ +
+ +
+ ); +}; +``` + +## Reference + +```ts +const [value, updateCookie, deleteCookie] = useCookie(cookieName: string); +``` diff --git a/package.json b/package.json index 5c6dbeed..b8330c07 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@xobotyi/scrollbar-width": "1.5.0", "copy-to-clipboard": "^3.2.0", "fast-shallow-equal": "^1.0.0", + "js-cookie": "^2.2.1", "nano-css": "^5.2.1", "react-fast-compare": "^2.0.4", "resize-observer-polyfill": "^1.5.1", @@ -79,6 +80,7 @@ "@storybook/react": "5.3.1", "@testing-library/react-hooks": "3.2.1", "@types/jest": "24.0.25", + "@types/js-cookie": "^2.2.4", "@types/react": "16.9.11", "babel-core": "6.26.3", "babel-loader": "8.0.6", diff --git a/src/index.ts b/src/index.ts index 4a0aee4f..dc6f65ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { default as useBattery } from './useBattery'; export { default as useBeforeUnload } from './useBeforeUnload'; export { default as useBoolean } from './useBoolean'; export { default as useClickAway } from './useClickAway'; +export { default as useCookie } from './useCookie'; export { default as useCopyToClipboard } from './useCopyToClipboard'; export { default as useCounter } from './useCounter'; export { default as useCss } from './useCss'; diff --git a/src/useCookie.ts b/src/useCookie.ts new file mode 100644 index 00000000..d8150088 --- /dev/null +++ b/src/useCookie.ts @@ -0,0 +1,25 @@ +import { useState, useCallback } from 'react'; +import Cookies from 'js-cookie'; + +const useCookie = ( + cookieName: string +): [string | null, (newValue: string, options?: Cookies.CookieAttributes) => void, () => void] => { + const [value, setValue] = useState(() => Cookies.get(cookieName) || null); + + const updateCookie = useCallback( + (newValue: string, options?: Cookies.CookieAttributes) => { + Cookies.set(cookieName, newValue, options); + setValue(newValue); + }, + [cookieName] + ); + + const deleteCookie = useCallback(() => { + Cookies.remove(cookieName); + setValue(null); + }, [cookieName]); + + return [value, updateCookie, deleteCookie]; +}; + +export default useCookie; diff --git a/stories/useCookie.story.tsx b/stories/useCookie.story.tsx new file mode 100644 index 00000000..89d3f753 --- /dev/null +++ b/stories/useCookie.story.tsx @@ -0,0 +1,31 @@ +import { storiesOf } from "@storybook/react"; +import React, { useState, useEffect } from "react"; +import { useCookie } from "../src"; +import ShowDocs from "./util/ShowDocs"; + +const Demo = () => { + const [value, updateCookie, deleteCookie] = useCookie("my-cookie"); + const [counter, setCounter] = useState(1); + + useEffect(() => { + deleteCookie(); + }, []); + + const updateCookieHandler = () => { + updateCookie(`my-awesome-cookie-${counter}`); + setCounter(c => c + 1); + }; + + return ( +
+

Value: {value}

+ +
+ +
+ ); +}; + +storiesOf("Side effects|useCookie", module) + .add("Docs", () => ) + .add("Demo", () => ); diff --git a/tests/useCookie.test.tsx b/tests/useCookie.test.tsx new file mode 100644 index 00000000..cc72dc46 --- /dev/null +++ b/tests/useCookie.test.tsx @@ -0,0 +1,68 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import Cookies from 'js-cookie'; +import { useCookie } from '../src'; + +const setup = (cookieName: string) => renderHook(() => useCookie(cookieName)); + +it('should have initial value of null if no cookie exists', () => { + const { result } = setup('some-cookie'); + + expect(result.current[0]).toBeNull(); +}); + +it('should have initial value of the cookie if it exists', () => { + const cookieName = 'some-cookie'; + const value = 'some-value'; + Cookies.set(cookieName, value); + + const { result } = setup(cookieName); + + expect(result.current[0]).toBe(value); + + // cleanup + Cookies.remove(cookieName); +}); + +it('should update the cookie on call to updateCookie', () => { + const spy = jest.spyOn(Cookies, 'set'); + + const cookieName = 'some-cookie'; + const { result } = setup(cookieName); + + const newValue = 'some-new-value'; + act(() => { + result.current[1](newValue); + }); + + expect(result.current[0]).toBe(newValue); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(cookieName, newValue, undefined); + + // cleanup + spy.mockRestore(); + Cookies.remove(cookieName); +}); + +it('should delete the cookie on call to deleteCookie', () => { + const cookieName = 'some-cookie'; + const value = 'some-value'; + Cookies.set(cookieName, value); + + const spy = jest.spyOn(Cookies, 'remove'); + + const { result } = setup(cookieName); + + expect(result.current[0]).toBe(value); + + act(() => { + result.current[2](); + }); + + expect(result.current[0]).toBeNull(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenLastCalledWith(cookieName); + + // cleanup + spy.mockRestore(); + Cookies.remove(cookieName); +}); diff --git a/yarn.lock b/yarn.lock index f6f27cf2..243c9796 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2929,6 +2929,11 @@ dependencies: jest-diff "^24.3.0" +"@types/js-cookie@^2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.4.tgz#f79720b4755aa197c2e15e982e2f438f5748e348" + integrity sha512-WTfSE1Eauak/Nrg6cA9FgPTFvVawejsai6zXoq0QYTQ3mxONeRtGhKxa7wMlUzWWmzrmTeV+rwLjHgsCntdrsA== + "@types/lolex@^2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@types/lolex/-/lolex-2.1.3.tgz#793557c9b8ad319b4c8e4c6548b90893f4aa5f69" @@ -8787,6 +8792,11 @@ jest@24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + js-levenshtein@^1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"