Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new hook useCookie #117

Merged
merged 5 commits into from
Jun 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ import { useMountEffect } from "@react-hookz/web/esnext";
— Manages a single LocalStorage key.
- [**`useSessionStorageValue`**](https://react-hookz.github.io/?path=/docs/side-effect-usesessionstoragevalue)
— Manages a single SessionStorage key.
- [**`useCookie`**](https://react-hookz.github.io/?path=/docs/side-effect-usecookie)
— Manages a single cookie.

- #### Sensor

Expand Down
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,20 @@
]
},
"dependencies": {
"@types/js-cookie": "^2.2.6",
"@types/react": "^17.0.3",
"@types/react-dom": "^17.0.3"
},
"peerDependencies": {
"js-cookie": "^2.2.1",
"react": "^16.8 || ^17",
"react-dom": "^16.8 || ^17"
},
"peerDependenciesMeta": {
"js-cookie": {
"optional": true
}
},
"devDependencies": {
"@babel/core": "^7.13.16",
"@commitlint/config-conventional": "^12.1.1",
Expand Down Expand Up @@ -113,6 +120,7 @@
"husky": "^4.3.8",
"jest": "^26.6.3",
"jest-github-actions-reporter": "^1.0.3",
"js-cookie": "^2.2.1",
"lint-staged": "^11.0.0",
"prettier": "^2.2.1",
"react": "^17.0.2",
Expand Down
8 changes: 7 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ export {
IValidatorDeferred,
IValidator,
IValidityState,
IUseValidatorReturn,
} from './useValidator/useValidator';

// Navigator
export { useNetworkState } from './useNetworkState/useNetworkState';
export {
useNetworkState,
IUseNetworkState,
INetworkInformation,
} from './useNetworkState/useNetworkState';

// Miscellaneous
export { useSyncedRef } from './useSyncedRef/useSyncedRef';

// SideEffect
export { useLocalStorageValue } from './useLocalStorageValue/useLocalStorageValue';
export { useSessionStorageValue } from './useSessionStorageValue/useSessionStorageValue';
export { useCookie, IUseCookieReturn } from './useCookie/useCookie';

// Sensor
export {
Expand Down
31 changes: 31 additions & 0 deletions src/useCookie/__docs__/example.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';
import { useCookie } from '../..';

export const Example: React.FC = () => {
const [cookie, set, remove] = useCookie('react-hookz', { expires: 3600 });

return (
<div>
<div>
<em>Cookie name:</em> react-hookz
</div>
<div>
<em>Cookie value:</em> {cookie}
</div>
<br />
<input
type="text"
value={cookie ?? ''}
onChange={(ev) => {
set(ev.target.value);
}}
placeholder="Enter cookie value here"
/>
<br />
<br />
<div>
<button onClick={remove}>remove cookie</button>
</div>
</div>
);
};
50 changes: 50 additions & 0 deletions src/useCookie/__docs__/story.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks';
import { Example } from './example.stories';

<Meta title="Side-effect/useCookie" component={Example} />

# useCookie

Manages a single cookie.

> Requires installation of `js-cookie` package.

- Uses `js-cookie` package underneath.
- SSR-friendly.
- Hooks that managing same key on same page - are synchronised. This synchronisation does not
involve cross-tabs sync or triggering on changes that performed by third-party code.

#### Example

<Canvas columns={3}>
<Story story={Example} inline />
<Story story={Example} inline />
<Story story={Example} inline />
</Canvas>

## Reference

```ts
export type IUseCookieReturn = [
value: undefined | null | string,
set: (value: string) => void,
remove: () => void,
fetch: () => void
];

export function useCookie(key: string, options?: Cookies.CookieAttributes): IUseCookieReturn;
```

#### Arguments

- **key** _`string`_ - Cookie name to manage.
- **options** _`Cookies.CookieAttributes`_ _(default: undefined)_ - Cookie options that will be
used during cookie set and delete.

#### Return

0. **state** - cookie value, `undefined` means it is not fetched yet, `null` means absence of
cookie.
1. **det** - Method to set new cookie value.
2. **remove** - Method to remove cookie.
3. **fetch** - Method to re-fetch cookie value.
132 changes: 132 additions & 0 deletions src/useCookie/__tests__/dom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { act, renderHook } from '@testing-library/react-hooks/dom';
import * as Cookies from 'js-cookie';
import { IUseCookieReturn, useCookie } from '../..';
import SpyInstance = jest.SpyInstance;

describe('useCookie', () => {
let getSpy: SpyInstance<{ [p: string]: string }, []>;
let setSpy: SpyInstance<
string | undefined,
[
name: string,
value: string | Record<string, any>,
options?: Cookies.CookieAttributes | undefined
]
>;
let removeSpy: SpyInstance<void, [name: string, options?: Cookies.CookieAttributes | undefined]>;

beforeAll(() => {
getSpy = jest.spyOn(Cookies, 'get');
setSpy = jest.spyOn(Cookies, 'set');
removeSpy = jest.spyOn(Cookies, 'remove');
});

afterAll(() => {
getSpy.mockRestore();
setSpy.mockRestore();
removeSpy.mockRestore();
});

beforeEach(() => {
getSpy.mockClear();
setSpy.mockClear();
removeSpy.mockClear();
});

it('should be defined', () => {
expect(useCookie).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useCookie('react-hookz'));
expect(result.error).toBeUndefined();
});

it('should return undefined on first render', () => {
const { result } = renderHook(() => useCookie('react-hookz'));
expect((result.all[0] as IUseCookieReturn)[0]).toBeUndefined();
});

it('should return null if cookie not exists', () => {
const { result } = renderHook(() => useCookie('react-hookz'));
expect(result.current[0]).toBe(null);
expect(getSpy).toBeCalledWith('react-hookz');
});

it('should set the cookie value on call to `set`', () => {
const { result } = renderHook(() => useCookie('react-hookz'));

expect(result.current[0]).toBe(null);
act(() => {
result.current[1]('awesome');
});
expect(result.current[0]).toBe('awesome');
expect(setSpy).toBeCalledWith('react-hookz', 'awesome', undefined);
Cookies.remove('react-hookz');
});

it('should remove cookie value on call to `remove`', () => {
const { result } = renderHook(() => useCookie('react-hookz'));

expect(result.current[0]).toBe(null);
act(() => {
result.current[1]('awesome');
});
expect(result.current[0]).toBe('awesome');

act(() => {
result.current[2]();
});
expect(result.current[0]).toBe(null);
expect(removeSpy).toBeCalledWith('react-hookz', undefined);
Cookies.remove('react-hookz');
});

it('should re-fetch cookie value on call to `fetch`', () => {
const { result } = renderHook(() => useCookie('react-hookz'));

Cookies.set('react-hookz', 'rulez');
expect(result.current[0]).toBe(null);
act(() => {
result.current[3]();
});
expect(result.current[0]).toBe('rulez');

Cookies.remove('react-hookz');
});

it('should be synchronized between several hooks managing same key', () => {
const { result: res1 } = renderHook(() => useCookie('react-hookz'));
const { result: res2 } = renderHook(() => useCookie('react-hookz'));

expect(res1.current[0]).toBe(null);
expect(res2.current[0]).toBe(null);

act(() => {
res1.current[1]('awesome');
});

expect(res1.current[0]).toBe('awesome');
expect(res2.current[0]).toBe('awesome');

act(() => {
res2.current[2]();
});

expect(res1.current[0]).toBe(null);
expect(res2.current[0]).toBe(null);
});

it('should return stable methods', () => {
const { result, rerender } = renderHook(() => useCookie('react-hookz'));

const res1 = result.current;

rerender();

expect(res1[1]).toBe(result.current[1]);
expect(res1[2]).toBe(result.current[2]);
expect(res1[3]).toBe(result.current[3]);
});
});
18 changes: 18 additions & 0 deletions src/useCookie/__tests__/ssr.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { renderHook } from '@testing-library/react-hooks/server';
import { useCookie } from '../..';

describe('useCookie', () => {
it('should be defined', () => {
expect(useCookie).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useCookie('react-hookz'));
expect(result.error).toBeUndefined();
});

it('should return undefined ', () => {
const { result } = renderHook(() => useCookie('react-hookz'));
expect(result.current[0]).toBeUndefined();
});
});
Loading