├── use-debounce
└── index.js
├── .gitignore
├── useWindow
└── index.js
├── babel.config.js
├── use-will-unmount
└── index.js
├── index.html
├── useError
└── index.js
├── use-did-update
└── index.js
├── .editorconfig
├── index.js
├── use-event-listener
└── index.js
├── use-offline
└── index.js
├── use-fetch
└── index.js
├── LICENSE.md
├── package.json
├── webpack.config.babel.js
├── off-the-hook.min.js
└── README.md
/use-debounce/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useDebounce } from 'react-use';
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 |
4 | # Node Modules and NPM
5 | node_modules
6 | package-lock.json
7 | npm-shrinkwrap.json
8 |
--------------------------------------------------------------------------------
/useWindow/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | export default function useWindow() => {
4 | // noop
5 | };
6 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'presets': [ '@babel/preset-flow', '@babel/preset-env', '@babel/preset-react' ],
3 | 'plugins': [ '@babel/plugin-proposal-class-properties'],
4 | }
5 |
--------------------------------------------------------------------------------
/use-will-unmount/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | export default function useWillUnmount(handler) {
4 | useEffect(() => {
5 | return () => {
6 | handler();
7 | };
8 | }, []);
9 | }
10 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Off the hook
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/useError/index.js:
--------------------------------------------------------------------------------
1 | // https://github.com/streamich/react-use/issues/64
2 | import React, { useState } from 'react';
3 |
4 | export default function useError() => {
5 | const [err, raise] = useState();
6 |
7 | if (err) {
8 | throw err;
9 | }
10 |
11 | return raise;
12 | };
13 |
--------------------------------------------------------------------------------
/use-did-update/index.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useLayoutEffect } from 'react';
2 |
3 | export default function useDidUpdate(handler) {
4 | const firstUpdate = useRef(true);
5 | useLayoutEffect(() => {
6 | if (firstUpdate.current) {
7 | firstUpdate.current = false;
8 | return;
9 | }
10 | handler();
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | max_line_length = 80
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | max_line_length = 0
14 | trim_trailing_whitespace = false
15 |
16 | [COMMIT_EDITMSG]
17 | max_line_length = 0
18 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import eventListenerHook from './use-event-listener';
2 | import offlineHook from './use-offline';
3 | import willUnmountHook from './use-will-unmount';
4 | import didUpdateHook from './use-did-update';
5 |
6 | export const useEventListener = eventListenerHook;
7 | export const useOffline = offlineHook;
8 | export const useWillUnmount = willUnmountHook;
9 | export const useDidUpdate = didUpdateHook;
10 |
--------------------------------------------------------------------------------
/use-event-listener/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | export default function useEventListener(eventName, eventHandler, target = 'window') {
4 | useEffect(() => {
5 | window.addEventListener(eventName, eventHandler);
6 |
7 | return () => {
8 | window.removeEventListener(eventName, eventHandler);
9 | };
10 | }, []);
11 |
12 | return isOffline;
13 | }
14 |
--------------------------------------------------------------------------------
/use-offline/index.js:
--------------------------------------------------------------------------------
1 | // https://www.robinwieruch.de/react-hooks/
2 | import React, { useState, useEffect } from 'react';
3 |
4 | export default function useOffline() {
5 | const [isOffline, setIsOffline] = useState(false);
6 |
7 | function onOffline() {
8 | setIsOffline(true);
9 | }
10 |
11 | function onOnline() {
12 | setIsOffline(false);
13 | }
14 |
15 | useEffect(() => {
16 | window.addEventListener('offline', onOffline);
17 | window.addEventListener('online', onOnline);
18 |
19 | return () => {
20 | window.removeEventListener('offline', onOffline);
21 | window.removeEventListener('online', onOnline);
22 | };
23 | }, []);
24 |
25 | return isOffline;
26 | }
27 |
--------------------------------------------------------------------------------
/use-fetch/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | export default function useFetch(input) => {
4 | const [error, setError] = useState(null);
5 | const [loading, setLoading] = useState(true);
6 | const [data, setData] = useState(null);
7 | useEffect(() => {
8 | (async () => {
9 | setLoading(true);
10 | try {
11 | const response = await fetch(input, {});
12 | if (response.ok) {
13 | const body = await body => body.json(response);
14 | setData(body);
15 | } else {
16 | setError(new Error(response.statusText));
17 | }
18 | } catch (e) {
19 | setError(e);
20 | }
21 | setLoading(false);
22 | })();
23 | }, [input, opts]);
24 | return { error, loading, data };
25 | };
26 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-present, Raphael Amorim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "off-the-hook",
3 | "version": "0.0.1",
4 | "description": "React Hooks that I use in my personal projects",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack -p",
8 | "start": "webpack-dev-server"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/raphamorim/off-the-hook.git"
13 | },
14 | "peerDependencies": {
15 | "react": "^16.8.1"
16 | },
17 | "devDependencies": {
18 | "@babel/core": "^7.0.0",
19 | "babel-loader": "^8.0.5",
20 | "@babel/plugin-proposal-class-properties": "^7.3.0",
21 | "@babel/preset-env": "^7.1.3",
22 | "@babel/preset-flow": "^7.0.0",
23 | "@babel/preset-react": "^7.0.0",
24 | "babel-jest": "24.1.0",
25 | "jest": "24.1.0",
26 | "prettier": "^1.5.3",
27 | "react": "^16.8.1",
28 | "webpack": "^4.29.3",
29 | "webpack-cli": "^3.2.3",
30 | "webpack-dev-server": ">=3.1.11"
31 | },
32 | "keywords": [
33 | "react",
34 | "hooks",
35 | "react",
36 | "hooks",
37 | "set",
38 | "toolset"
39 | ],
40 | "author": "Raphael Amorim ",
41 | "license": "MIT",
42 | "bugs": {
43 | "url": "https://github.com/raphamorim/off-the-hook/issues"
44 | },
45 | "homepage": "https://github.com/raphamorim/off-the-hook#readme"
46 | }
47 |
--------------------------------------------------------------------------------
/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | const sourcePath = __dirname;
5 |
6 | const config = {
7 | target: 'web',
8 | mode: 'production',
9 | entry: [path.resolve(sourcePath, 'index.js')],
10 | output: {
11 | path: __dirname,
12 | library: 'off-the-hook',
13 | libraryTarget: 'umd',
14 | umdNamedDefine: true,
15 | filename: 'off-the-hook.min.js',
16 | },
17 | optimization: {
18 | minimize: true
19 | },
20 | externals: {
21 | // Use external version of React
22 | react: {
23 | root: 'React',
24 | commonjs2: 'react',
25 | commonjs: 'react',
26 | amd: 'react'
27 | }
28 | },
29 | resolve: {
30 | extensions: ['.js', '.jsx'],
31 | modules: [
32 | path.resolve(__dirname, 'node_modules'),
33 | ],
34 | },
35 | module: {
36 | rules: [
37 | {
38 | test: /\.(js|jsx)$/,
39 | exclude: /node_modules/,
40 | use: ['babel-loader'],
41 | include: [sourcePath],
42 | },
43 | ],
44 | },
45 | plugins: [new webpack.NamedModulesPlugin()],
46 | devServer: {
47 | host: '0.0.0.0',
48 | open: true,
49 | port: 9000,
50 | historyApiFallback: {
51 | index: path.join(__dirname, 'index.html'),
52 | },
53 | },
54 | };
55 |
56 | module.exports = config;
57 |
--------------------------------------------------------------------------------
/off-the-hook.min.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define("off-the-hook",["react"],t):"object"==typeof exports?exports["off-the-hook"]=t(require("react")):e["off-the-hook"]=t(e.React)}(window,function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}({"./index.js":function(e,t,n){"use strict";n.r(t);var r=n("react");function o(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=[],r=!0,o=!1,i=void 0;try{for(var u,f=e[Symbol.iterator]();!(r=(u=f.next()).done)&&(n.push(u.value),!t||n.length!==t);r=!0);}catch(e){o=!0,i=e}finally{try{r||null==f.return||f.return()}finally{if(o)throw i}}return n}(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}n.d(t,"useOffline",function(){return i});var i=function(){var e=o(Object(r.useState)(!1),2),t=e[0],n=e[1];function i(){n(!0)}function u(){n(!1)}return Object(r.useEffect)(function(){return window.addEventListener("offline",i),window.addEventListener("online",u),function(){window.removeEventListener("offline",i),window.removeEventListener("online",u)}},[]),t}},0:function(e,t,n){e.exports=n("./index.js")},react:function(t,n){t.exports=e}})});
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # off-the-hook
2 |
3 | > https://reactjs.org/docs/hooks-custom.html
4 |
5 | When we want to share logic between two JavaScript functions, we extract it to a third function. Both components and Hooks are functions, so this works for them too!
6 |
7 | A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks. This package is a set of custom hooks that I use in my personal projects.
8 |
9 | ## Hooks
10 |
11 | #### [`useEventListener`](#useeventlistener)
12 |
13 | You can also specify the target just passing it as the third argument (default is `window`). It can be used to listen events of refs, e.g: check if the image is loaded, listen a custom Event [...]
14 |
15 | ```jsx
16 | import React from 'react';
17 | import { useEventListener, useRef, useState } from 'off-the-hook';
18 |
19 | function App() {
20 | const [ content, setContent ] = useState('Is not scrolling');
21 | const elementRef = useRef(null);
22 |
23 | // listen to scroll events on window
24 | useEventListener('scroll', () => setContent('Scrolling...'));
25 |
26 | // listen to resize events on document
27 | useEventListener('resize', () => setContent('Resizing...'), document);
28 |
29 | // Listen to click events on `elementRef`
30 | useEventListener('click', () => setContent('Clicked!'), elementRef);
31 |
32 | return {content}
;
33 | };
34 | ```
35 |
36 | #### [`useFetch`](#usefetch)
37 |
38 | ```jsx
39 | import React from 'react';
40 | import { useFetch } from 'off-the-hook';
41 |
42 | function App() {
43 | const { error, data, loading } = useFetch('https://myapi.com/user/123');
44 | if (loading) {
45 | return Loading...
;
46 | }
47 |
48 | if (error) {
49 | return {error.message}
;
50 | }
51 |
52 | return {data.user}
;
53 | };
54 | ```
55 |
56 | #### [`useError`](#useError)
57 |
58 | Hook that creates a dispatcher for errors, that can be caught with error boundaries.
59 |
60 | ```jsx
61 | import React from 'react';
62 | import { useError } from 'off-the-hook';
63 |
64 | const Demo = () => {
65 | const [ raiseError ] = useError();
66 |
67 | useEffect(() => {
68 | raiseError(
69 | new Error(
70 | "Could not work with setTimeout for some reason!"
71 | )
72 | )
73 | });
74 |
75 | return Noice
;
76 | };
77 | ```
78 |
79 | #### [`useWindow`](#usewindow)
80 |
81 | You can just check window properties.
82 |
83 | ```jsx
84 | import React from 'react';
85 | import { useWindow } from 'off-the-hook';
86 |
87 | const Demo = () => {
88 | const { width, height, screen } = useWindow();
89 |
90 | return { width }px, { height }px
91 | };
92 | ```
93 |
94 | Also listen for specific properties changes
95 |
96 | ```jsx
97 | import React from 'react';
98 | import { useWindow, useState } from 'off-the-hook';
99 |
100 | const Demo = () => {
101 | const [ status, setStatus ] = useState('');
102 | const { navigator: { connection } } = useWindow(
103 | () => { setStatus('updating...') },
104 | ['navigator.connection']
105 | );
106 |
107 | return (
108 | Your downlink is: { connection.downlink }
109 | { status }
110 | };
111 | ```
112 |
113 | #### [`useDebounce`](#usedebounce)
114 |
115 | Inspired API by [react-use's useDebounce](https://github.com/streamich/react-use/blob/master/docs/useDebounce.md).
116 |
117 | The debounce timeout will start when one of the values in third argument changes.
118 |
119 | ```jsx
120 | import React from 'react';
121 | import { useDebounce, useState } from 'off-the-hook';
122 |
123 | const Demo = () => {
124 | const [ content, setContent ] = useState('Content Placeholder');
125 |
126 | useDebounce(() => setContent('Content Placeholder'), 1000, [ content ]);
127 | return (
128 | { setContent('Clicked') }}>
129 | { content }
130 |
131 | );
132 | };
133 | ```
134 |
135 | #### [`useWillUnmount`](#usewillunmount)
136 |
137 | Similar to `componentWillUnmount`. Dispatchs a handler when the component will unmount.
138 |
139 | ```jsx
140 | import React from 'react';
141 | import { useWillUnmount } from 'off-the-hook';
142 |
143 | function App() {
144 | useWillUnmount(() => alert('componentWillUnmount'));
145 | return Noice
;
146 | };
147 | ```
148 |
149 | #### [`useDidUpdate`](#usecomponentunmount)
150 |
151 | Similar to `componentDidUpdate`. Dispatchs a handler when the component just updated.
152 |
153 | ```jsx
154 | import React from 'react';
155 | import { useDidUpdate, useState } from 'off-the-hook';
156 |
157 | function App() {
158 | const [count, setCount] = useState(0);
159 |
160 | useDidUpdate(() => alert('componentDidUpdate'));
161 |
162 | return (
163 |
164 | {count}
165 |
168 |
169 | );
170 | };
171 | ```
172 |
173 | #### [`useOffline`](#useoffline)
174 |
175 | ```jsx
176 | import React from 'react';
177 | import { useOffline } from 'off-the-hook';
178 |
179 | const App = () => (
180 | useOffline() ?
181 | Offline! :
182 | Online!
183 | );
184 | ```
185 |
186 | > Raphael Amorim 2019
187 |
--------------------------------------------------------------------------------