├── 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 | --------------------------------------------------------------------------------