├── LICENSE ├── useBroadcast.js ├── useClickOutside.js ├── useDOMState.js ├── useData.js ├── useDelay.js ├── useDocumentTitle.js ├── useEvent.js ├── useFetch.js ├── useFirebase.js ├── useForm.js ├── useFormInput.js ├── useGeolocation.js ├── useImmer.js ├── useInterval.js ├── useIsOnline.js ├── useKeyLog.js ├── useKeyPress.js ├── useMedia.js ├── useMousePosition.js ├── usePromise.js ├── useRouter.js ├── useScrollDetection.js ├── useStateListener.js ├── useStorage.js ├── useStuck.js ├── useToggle.js ├── useWindowHeight.js └── useWindowWidth.js /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /useBroadcast.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import useStateListener from "./useStateListener"; 3 | 4 | export default function useBroadcast(channelName = "default") { 5 | const channel = useRef(new BroadcastChannel(channelName)); 6 | const postMessage = msg => channel.current.postMessage(JSON.stringify(msg)); 7 | const { state } = useStateListener( 8 | "message", 9 | e => JSON.parse(e.data), 10 | channel.current 11 | ); 12 | return [state, postMessage]; 13 | } 14 | -------------------------------------------------------------------------------- /useClickOutside.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import useEvent from "./useEvent"; 3 | 4 | export default function useClickOutside(fn) { 5 | const ref = useRef(null); 6 | useEvent("click", e => ref.current.contains(e.target) || fn(), document); 7 | return ref; 8 | }; 9 | -------------------------------------------------------------------------------- /useDOMState.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useDOMState() { 4 | return useStateListener("DOMContentLoaded", () => document.readyState).state; 5 | }; 6 | -------------------------------------------------------------------------------- /useData.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Vue.js/Immmer objects with automatic change detection 3 | * 4 | * Notice: unlike Vue and Immer, the change detection is not recursive! 5 | * 6 | * Usage: 7 | * 8 | * const data = useData({ count: 0 }); 9 | * 10 | * return ; 11 | * 12 | */ 13 | 14 | export default function useData(data) { 15 | const [state, setState] = useState(data); 16 | return new Proxy(state, { 17 | set: (obj, prop, value) => setState({ ...state, [prop]: value}) 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /useDelay.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function useDelay(interval = 1000) { 4 | const [done, setDone] = useState(false); 5 | useEffect(() => { 6 | let id = setTimeout(() => { setDone(true); }, interval); 7 | return () => clearTimeout(id); 8 | }, [done]); 9 | return done; 10 | }; 11 | -------------------------------------------------------------------------------- /useDocumentTitle.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export default function useDocumentTitle(title) { 4 | useEffect(() => { 5 | document.title = title; 6 | }, [title]); 7 | }; 8 | -------------------------------------------------------------------------------- /useEvent.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export default function useEvent(event, handler, source = window) { 4 | useEffect(() => { 5 | source.addEventListener(event, handler); 6 | return () => { 7 | source.removeEventListener(event, handler); 8 | }; 9 | }, [handler]); 10 | }; 11 | -------------------------------------------------------------------------------- /useFetch.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export default function useFetch(url) { 4 | const [data, setData] = useState({ loading: true }); 5 | useEffect(() => { 6 | let active = true; 7 | fetch(url) 8 | .then(response => response.json()) 9 | .then(data => active && setData({ data })) 10 | .catch(error => setData({ error })); 11 | return () => (active = false); 12 | }, [url]); 13 | return data; 14 | } 15 | -------------------------------------------------------------------------------- /useFirebase.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useFirebase(project, path = "/") { 4 | const url = `https://${project}.firebaseio.com/${path}.json`; 5 | const source = new EventSource(url); 6 | return useStateListener("put", ({ data }) => JSON.parse(data).data, source) 7 | .state; 8 | } 9 | -------------------------------------------------------------------------------- /useForm.js: -------------------------------------------------------------------------------- 1 | /* Usage: 2 | * 3 | * const [values, { radio, checkbox, tel }] = useForm({accept: "y", phone: "123"}); 4 | * 5 | * return ( 6 | *
7 | * 8 | * 9 | * 10 | * 11 | *
12 | * ); 13 | */ 14 | export default function useForm(value) { 15 | const [values, setValues] = useState(value); 16 | const [touched, setTouchedState] = useState({}); 17 | const [validity, setValidityState] = useState({}); 18 | 19 | const generate = type => (name, ownValue) => { 20 | const isCheck = type === "checkbox"; 21 | const isRadio = type === "radio"; 22 | return { 23 | name, 24 | type, 25 | get checked() { 26 | return isRadio ? ownValue == values[name] : values[name]; 27 | }, 28 | get value() { 29 | return isRadio ? ownValue : values[name]; 30 | }, 31 | onChange({ target: { value, checked } }) { 32 | console.log(value, checked) 33 | setValues({ ...values, [name]: isCheck ? checked : value }); 34 | }, 35 | onBlur(e) { 36 | setTouchedState({ ...touched, [name]: true }); 37 | setValidityState({ ...validity, [name]: e.target.validity.valid }); 38 | } 39 | }; 40 | }; 41 | const factory = { 42 | input: generate("input"), 43 | tel: generate("tel"), 44 | checkbox: generate("checkbox"), 45 | radio: generate("radio") 46 | }; 47 | 48 | return [{ values, validity, touched }, factory]; 49 | } 50 | -------------------------------------------------------------------------------- /useFormInput.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function useFormInput(initalValue) { 4 | const [value, setValue] = useState(initalValue); 5 | const onChange = e => setValue(e.currentTarget.value); 6 | return { value, onChange }; 7 | }; 8 | -------------------------------------------------------------------------------- /useGeolocation.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export default function useGeolocation() { 4 | const [location, setLocation] = useState({}); 5 | const handler = ({coords}) => setLocation(coords); 6 | useEffect(() => { 7 | navigator.geolocation.getCurrentPosition(handler); 8 | const id = navigator.geolocation.watchPosition(handler); 9 | return () => navigator.geolocation.clearWatch(id); 10 | }, []); 11 | return location; 12 | }; 13 | -------------------------------------------------------------------------------- /useImmer.js: -------------------------------------------------------------------------------- 1 | import { useReducer } from "react"; 2 | import produce from "immer"; 3 | 4 | export default function useImmutable(actions, initialState) { 5 | return useReducer( 6 | produce((state, { type, payload }) => actions[type](state)(payload)), 7 | initialState 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /useInterval.js: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from "react"; 2 | 3 | export default function useInterval(cb, time = 1000) { 4 | const ref = useRef(); 5 | useEffect(() => { 6 | ref.current = cb; 7 | const id = setInterval(() => ref.current(), time); 8 | return () => clearInterval(id); 9 | }, [time]); 10 | }; 11 | -------------------------------------------------------------------------------- /useIsOnline.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import useEvent from "./useEvent"; 3 | 4 | export default function useOnlineStatus() { 5 | const [status, setStatus] = useState(navigator && navigator.onLine); 6 | useEvent("online", () => setStatus(true)); 7 | useEvent("offline", () => setStatus(false)); 8 | return status; 9 | }; 10 | -------------------------------------------------------------------------------- /useKeyLog.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useKeyLog(handler) { 4 | return useStateListener("keypress", (e, state) => [e.key, ...state], document) 5 | .state; 6 | } 7 | -------------------------------------------------------------------------------- /useKeyPress.js: -------------------------------------------------------------------------------- 1 | import useEvent from "./useEvent"; 2 | 3 | export default function useKeyPress(handler) { 4 | useEvent("keypress", handler, document); 5 | }; 6 | -------------------------------------------------------------------------------- /useMedia.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useMedia(query) { 4 | return useStateListener("resize", () => matchMedia(query).matches).state; 5 | } 6 | -------------------------------------------------------------------------------- /useMousePosition.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export const useMousePosition = () => { 4 | return useStateListener("mousemove", e => ({ 5 | x: e && e.clientX, 6 | y: e && e.clientY 7 | })).state; 8 | }; 9 | -------------------------------------------------------------------------------- /usePromise.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function usePromise(func) { 4 | const [state, setState] = useState({}); 5 | const handler = (...args) => { 6 | setState({ pending: true }); 7 | func(...args) 8 | .then(res => setState({ res })) 9 | .catch(error => setData({ error })); 10 | }; 11 | return [state, handler]; 12 | }; 13 | -------------------------------------------------------------------------------- /useRouter.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useRouter() { 4 | const { state, setter } = useStateListener("popstate", () => location.pathname); 5 | const push = path => { 6 | history.pushState(null, "", path); 7 | setter(path); 8 | }; 9 | return [state, push]; 10 | }; 11 | -------------------------------------------------------------------------------- /useScrollDetection.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useScrollDetection(refs, defaultSection) { 4 | return useStateListener("scroll", () => 5 | refs.find( 6 | ref => 7 | ref && ref instanceof Element && ref.getBoundingClientRect().top < 0 8 | ) 9 | ).state; 10 | } 11 | -------------------------------------------------------------------------------- /useStateListener.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export default function useStateListener(event, callback, source = window) { 4 | const [state, setState] = useState(callback()); 5 | useEffect(() => { 6 | const handler = e => setState(callback(e, state)); 7 | source.addEventListener(event, handler); 8 | return () => { 9 | source.removeEventListener(event, handler); 10 | }; 11 | }, [callback, event, source]); 12 | return { state, setter }; 13 | } 14 | -------------------------------------------------------------------------------- /useStorage.js: -------------------------------------------------------------------------------- 1 | export default function useStorage(key, initialValue) { 2 | const [localValue, setLocalValue] = useState( 3 | () => JSON.parse(localStorage.getItem(key)) || initialValue 4 | ); 5 | 6 | const setValue = value => { 7 | setLocalValue(value); 8 | localStorage.setItem(key, JSON.stringify(value)); 9 | }; 10 | 11 | useEvent("storage", e => { 12 | if (e.key === key) { 13 | setLocalValue(JSON.parse(localStorage.getItem(key))); 14 | } 15 | }); 16 | 17 | return [localValue, setValue]; 18 | }; 19 | -------------------------------------------------------------------------------- /useStuck.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useStuck(ref, parent = ".wrapper") { 4 | return useStateListener( 5 | "scroll", 6 | () => `translate(0, ${ref.current.closest(parent).scrollTop}px)` 7 | ).state; 8 | }; 9 | -------------------------------------------------------------------------------- /useToggle.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | export default function useToggle(initialValue = false) { 4 | const [state, setState] = useState(initialValue); 5 | const toggle = () => setState(!state); 6 | return [state, toggle, setState]; 7 | }; 8 | -------------------------------------------------------------------------------- /useWindowHeight.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useWindowSize() { 4 | return useStateListener("resize", () => innerHeight).state; 5 | }; 6 | -------------------------------------------------------------------------------- /useWindowWidth.js: -------------------------------------------------------------------------------- 1 | import useStateListener from "./useStateListener"; 2 | 3 | export default function useWindowSize() { 4 | return useStateListener("resize", () => innerWidth).state; 5 | } 6 | --------------------------------------------------------------------------------