diff --git a/.npmignore b/.npmignore index dc58000..aa6ba0e 100755 --- a/.npmignore +++ b/.npmignore @@ -1,11 +1,10 @@ src __tests__ coverage - +.husky tsconfig.json .travis.yml .prettierrc - .eslintrc.js rollup.config.js yarn-error.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bdff8c..3c1f3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,6 @@ ## 1.0.0 (Soon) -#### New Feature +### New Feature - added `useCustomEvent`, `useEmitter` & `useListener` hooks diff --git a/README.md b/README.md index 5cf6fcc..69ddfa2 100755 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![NPM downloads](https://img.shields.io/npm/dm/react-custom-events-hooks?style=flat-square)](https://www.npmjs.com/package/react-custom-events-hooks) [![NPM license](https://img.shields.io/npm/l/react-custom-events-hooks?style=flat-square)](https://www.npmjs.com/package/react-custom-events-hooks) [![Codecov](https://img.shields.io/codecov/c/github/cool-hooks/react-custom-events-hooks?style=flat-square)](https://codecov.io/gh/cool-hooks/react-custom-events-hooks) -[![Travis](https://img.shields.io/travis/cool-hooks/react-custom-events-hooks/master?style=flat-square)](https://travis-ci.org/cool-hooks/react-custom-events-hooks) +[![Travis](https://img.shields.io/travis/com/cool-hooks/react-custom-events-hooks/master?style=flat-square)](https://app.travis-ci.com/github/cool-hooks/react-custom-events-hooks) [![Bundle size](https://img.shields.io/bundlephobia/min/react-custom-events-hooks?style=flat-square)](https://bundlephobia.com/result?p=react-custom-events-hooks) ## About @@ -33,7 +33,7 @@ $ yarn add react-custom-events-hooks ## Getting Started -**• Import hooks in React application file:** +**• Import hooks in the React application file:** ```js import { @@ -43,7 +43,7 @@ import { } from 'react-custom-events-hooks'; ``` -#### Example +### Example ```js import React, { useState } from 'react'; diff --git a/__tests__/useCustomEvent.test.ts b/__tests__/useCustomEvent.test.ts index 2173ec1..491e61a 100644 --- a/__tests__/useCustomEvent.test.ts +++ b/__tests__/useCustomEvent.test.ts @@ -3,7 +3,7 @@ import { renderHook } from '@testing-library/react-hooks'; import { useCustomEvent } from '../src'; describe('useCustomEvent', () => { - it('should', () => { + it('should emit custom event', () => { const { result } = renderHook(() => useCustomEvent({ eventName: 'myAwesomeCustomEvent' }) ); @@ -11,7 +11,7 @@ describe('useCustomEvent', () => { result.current('hello'); }); - it('should', () => { + it('should emit and listen to custom event', () => { const onSignal = jest.fn(); const { result } = renderHook(() => @@ -34,4 +34,7 @@ describe('useCustomEvent', () => { message: 'world', }); }); + + it.todo('custom element'); + it.todo('options'); }); diff --git a/__tests__/useEmitter.test.ts b/__tests__/useEmitter.test.ts index a03c791..f92e087 100644 --- a/__tests__/useEmitter.test.ts +++ b/__tests__/useEmitter.test.ts @@ -2,16 +2,37 @@ import { renderHook } from '@testing-library/react-hooks'; import { useEmitter } from '../src'; +// TODO refactor describe('useEmitter', () => { - it('should', () => { + it('should emit custom event', () => { const { result } = renderHook(() => useEmitter('myAwesomeCustomEvent')); result.current({ title: 'hello', }); + + window.addEventListener('myAwesomeCustomEvent', (e) => { + expect(e.detail).toStrictEqual({ + title: 'hello', + }); + }); }); - it('should', () => { - const { result } = renderHook(() => null); + it('should emit custom event on element', () => { + const element = document.createElement('div'); + + const { result } = renderHook(() => + useEmitter('myAwesomeCustomEvent', element) + ); + + result.current({ + title: 'hello', + }); + + element.addEventListener('myAwesomeCustomEvent', (e) => { + expect(e.detail).toStrictEqual({ + title: 'hello', + }); + }); }); }); diff --git a/__tests__/useListener.test.ts b/__tests__/useListener.test.ts index 68c3dc3..6419acf 100644 --- a/__tests__/useListener.test.ts +++ b/__tests__/useListener.test.ts @@ -1,37 +1,101 @@ import { renderHook } from '@testing-library/react-hooks'; -import { useCustomEvent, useListener } from '../src'; +import { useListener } from '../src'; describe('useListener', () => { - it('should', () => { - const { result } = renderHook(() => - useCustomEvent({ eventName: 'myAwesomeCustomEvent' }) - ); + // it('should', () => { + // const onSignal = jest.fn(); - result.current('hello'); - }); + // const { result } = renderHook(() => + // useListener({ + // eventName: 'myAwesomeCustomEvent', + // defaultValue: { + // sender: 'dummy-sender', + // }, + // onSignal, + // }) + // ); + + // result.current({ + // title: 'hello', + // message: 'world', + // }); + + // expect(onSignal).toHaveBeenCalledWith({ + // title: 'hello', + // message: 'world', + // }); + // }); - it('should', () => { + it('should call onSignal when custom event is emitted', () => { const onSignal = jest.fn(); - const { result } = renderHook(() => - useCustomEvent({ + renderHook(() => + useListener({ eventName: 'myAwesomeCustomEvent', - defaultValue: { - sender: 'dummy-sender', - }, onSignal, }) ); - result.current({ + window.dispatchEvent( + new CustomEvent('myAwesomeCustomEvent', { + detail: { + title: 'hello', + message: 'world', + }, + }) + ); + + expect(onSignal).toHaveBeenCalledWith({ title: 'hello', message: 'world', }); + }); - expect(onSignal).toHaveBeenCalledWith({ + // it.todo('onSignal call') + // it.todo('no onSignal') ? + + // TODO custom element + it('should ', () => { + const onSignalElement = jest.fn(); + const onSignalWindow = jest.fn(); + + const element = document.createElement('div'); + + renderHook(() => + useListener({ + eventName: 'myAwesomeCustomEvent', + onSignal: onSignalElement, + element, + }) + ); + + renderHook(() => + useListener({ + eventName: 'myAwesomeCustomEvent', + onSignal: onSignalWindow, + }) + ); + + element.dispatchEvent( + new CustomEvent('myAwesomeCustomEvent', { + detail: { + title: 'hello', + message: 'world', + }, + }) + ); + + expect(onSignalElement).toHaveBeenCalledWith({ title: 'hello', message: 'world', }); + + expect(onSignalWindow).not.toHaveBeenCalled(); }); + + it.todo('multiple calls one by one'); + + // it.todo('custom element') + it.todo('options'); }); diff --git a/package.json b/package.json index 7b315e7..a46b7f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-custom-events-hooks", - "version": "0.1.0", + "version": "1.0.0", "description": "Create custom events. Fast, simple, fun!", "author": "Jakub Biesiada", "license": "MIT", @@ -17,7 +17,9 @@ "lint": "eslint 'src/**/*.{tsx,ts}' --fix", "prettier": "prettier --write 'src/**/*.{tsx,ts}'", "commit": "git-cz", - "semantic-release": "semantic-release" + "semantic-release": "semantic-release", + "prepare": "husky install", + "pre-commit": "lint-staged" }, "repository": { "type": "git", @@ -25,11 +27,12 @@ }, "keywords": [ "react", + "hooks", "events", - "emitter", - "listener", "observable", - "hooks" + "listener", + "signals", + "emitter" ], "bugs": { "url": "https://github.com/cool-hooks/react-custom-events-hooks/issues" @@ -41,7 +44,6 @@ "@testing-library/react-hooks": "^3.4.2", "@types/jest": "^26.0.15", "@types/react": "^16.9.56", - "@types/react-router": "^5.1.8", "@typescript-eslint/parser": "^4.7.0", "cz-conventional-changelog": "3.3.0", "cz-emoji": "^1.3.1", @@ -77,11 +79,6 @@ "^.+\\.tsx?$": "ts-jest" } }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, "lint-staged": { "src/**/*.{tsx,ts}": [ "npm run prettier", diff --git a/rollup.config.js b/rollup.config.js index 0d80503..7986fb4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -19,5 +19,5 @@ export default { }, ], plugins: [resolve(), typescript(), terser()], - external: Object.keys(pkg.peerDependencies), + external: Object.keys(pkg.peerDependencies || {}), }; diff --git a/src/hooks/useCustomEvent.ts b/src/hooks/useCustomEvent.ts index dd03f7d..0c3763b 100644 --- a/src/hooks/useCustomEvent.ts +++ b/src/hooks/useCustomEvent.ts @@ -1,14 +1,9 @@ +import { useMemo } from 'react'; + import { useListener } from './useListener'; import { useEmitter } from './useEmitter'; -import type { Element, Options } from '../types'; - -interface Params { - readonly eventName: string; - onSignal?: (e: CustomEvent) => void; - readonly element?: Element; - readonly options?: Options; -} +import type { Params } from '../types'; export const useCustomEvent = ({ eventName, @@ -16,9 +11,9 @@ export const useCustomEvent = ({ element = window, options = {}, }: Params) => { - const handleSignal = onSignal || (() => null); + const handleSignal = useMemo(() => onSignal || (() => null), [onSignal]); - useListener(eventName, handleSignal, element, options); + useListener({ eventName, onSignal: handleSignal, element, options }); return useEmitter(eventName, element); }; diff --git a/src/hooks/useEmitter.ts b/src/hooks/useEmitter.ts index a9f3300..ee55069 100644 --- a/src/hooks/useEmitter.ts +++ b/src/hooks/useEmitter.ts @@ -1,11 +1,24 @@ -import type { Element } from '../types'; +import { useCallback } from 'react'; -export const useEmitter = (eventName: string, element: Element = window) => { - const callEvent = (data: T) => { - const event = new CustomEvent(eventName, { detail: data }); +import type { ObservedElement } from '../types'; - element.dispatchEvent(event); - }; +type CallEventCallback = (data: T) => void; + +export const useEmitter = ( + eventName: string, + element: ObservedElement = window +) => { + // TODO add options + const callEvent = useCallback>( + (data) => { + const event = new CustomEvent(eventName, { + detail: data /* ...options */, + }); + + element.dispatchEvent(event); + }, + [element, eventName] + ); return callEvent; }; diff --git a/src/hooks/useListener.ts b/src/hooks/useListener.ts index a60787e..739f368 100644 --- a/src/hooks/useListener.ts +++ b/src/hooks/useListener.ts @@ -1,18 +1,16 @@ import { useEffect } from 'react'; -import type { Element, Options } from '../types'; +import type { Params } from '../types'; -export const useListener = ( - eventName: string, - onSignal: (e: CustomEvent) => void, - element: Element = window, - options: Options = {} -) => { +export const useListener = ({ + eventName, + onSignal, + element = window, + options = {}, +}: Params) => { useEffect(() => { if (typeof onSignal === 'function') { - const handleSignal = (e: Event) => { - onSignal(e as CustomEvent); - }; + const handleSignal = (e: Event) => onSignal(e as CustomEvent); element.addEventListener(eventName, handleSignal, options); diff --git a/src/types/Element.ts b/src/types/Element.ts deleted file mode 100644 index 6452252..0000000 --- a/src/types/Element.ts +++ /dev/null @@ -1 +0,0 @@ -export type Element = HTMLElement | Window | Document; diff --git a/src/types/ObservedElement.ts b/src/types/ObservedElement.ts new file mode 100644 index 0000000..53f454c --- /dev/null +++ b/src/types/ObservedElement.ts @@ -0,0 +1 @@ +export type ObservedElement = HTMLElement | Window | Document; diff --git a/src/types/Options.ts b/src/types/Options.ts index 389674c..4d3a2fe 100644 --- a/src/types/Options.ts +++ b/src/types/Options.ts @@ -1,2 +1 @@ -/* eslint-disable no-undef */ export type Options = boolean | AddEventListenerOptions; diff --git a/src/types/Params.ts b/src/types/Params.ts new file mode 100644 index 0000000..d172999 --- /dev/null +++ b/src/types/Params.ts @@ -0,0 +1,8 @@ +import type { ObservedElement, Options } from '.'; + +export interface Params { + readonly eventName: string; + readonly onSignal: (e: CustomEvent) => void; + readonly element: ObservedElement; + readonly options: Options; +} diff --git a/src/types/index.ts b/src/types/index.ts index c18722c..6ab5aff 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,2 +1,3 @@ -export type { Element } from './Element'; +export type { ObservedElement } from './ObservedElement'; export type { Options } from './Options'; +export type { Params } from './Params'; diff --git a/yarn.lock b/yarn.lock index 698e988..466ddbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1083,11 +1083,6 @@ dependencies: "@types/node" "*" -"@types/history@*": - version "4.7.7" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.7.tgz#613957d900fab9ff84c8dfb24fa3eef0c2a40896" - integrity sha512-2xtoL22/3Mv6a70i4+4RB7VgbDDORoWwjcqeNysojZA0R7NK17RbY5Gof/2QiFfJgX+KkWghbwJ+d/2SB8Ndzg== - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -1163,14 +1158,6 @@ version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" -"@types/react-router@^5.1.8": - version "5.1.8" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.8.tgz#4614e5ba7559657438e17766bb95ef6ed6acc3fa" - integrity sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg== - dependencies: - "@types/history" "*" - "@types/react" "*" - "@types/react-test-renderer@*": version "16.9.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5"