Last active
January 12, 2023 19:45
-
-
Save andresgcarmona/8a26ee05e307599cfc2e0060e35cc271 to your computer and use it in GitHub Desktop.
React hooks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function useEventListener(eventName, handler, element = window) { | |
// Create a ref that stores handler | |
const savedHandler = useRef(); | |
// Update ref.current value if handler changes. | |
// This allows our effect below to always get latest handler ... | |
// ... without us needing to pass it in effect deps array ... | |
// ... and potentially cause effect to re-run every render. | |
useEffect(() => { | |
savedHandler.current = handler; | |
}, [handler]); | |
useEffect( | |
() => { | |
// Make sure element supports addEventListener | |
// On | |
const isSupported = element && element.addEventListener; | |
if (!isSupported) return; | |
// Create event listener that calls handler function stored in ref | |
const eventListener = (event) => savedHandler.current(event); | |
// Add event listener | |
element.addEventListener(eventName, eventListener); | |
// Remove event listener on cleanup | |
return () => { | |
element.removeEventListener(eventName, eventListener); | |
}; | |
}, | |
[eventName, element] // Re-run if eventName or element changes | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const myFunction = (): Promise<string> => { | |
return new Promise((resolve, reject) => { | |
setTimeout(() => { | |
const rnd = Math.random() * 10; | |
rnd <= 5 | |
? resolve("Submitted successfully 🙌") | |
: reject("Oh no there was an error 😞"); | |
}, 2000); | |
}); | |
}; | |
// Hook | |
const useAsync = <T, E = string>( | |
asyncFunction: () => Promise<T>, | |
immediate = true | |
) => { | |
const [status, setStatus] = useState< | |
"idle" | "pending" | "success" | "error" | |
>("idle"); | |
const [value, setValue] = useState<T | null>(null); | |
const [error, setError] = useState<E | null>(null); | |
// The execute function wraps asyncFunction and | |
// handles setting state for pending, value, and error. | |
// useCallback ensures the below useEffect is not called | |
// on every render, but only if asyncFunction changes. | |
const execute = useCallback(() => { | |
setStatus("pending"); | |
setValue(null); | |
setError(null); | |
return asyncFunction() | |
.then((response: any) => { | |
setValue(response); | |
setStatus("success"); | |
}) | |
.catch((error: any) => { | |
setError(error); | |
setStatus("error"); | |
}); | |
}, [asyncFunction]); | |
// Call execute if we want to fire it right away. | |
// Otherwise execute can be called later, such as | |
// in an onClick handler. | |
useEffect(() => { | |
if (immediate) { | |
execute(); | |
} | |
}, [execute, immediate]); | |
return { execute, status, value, error }; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function useDarkMode() { | |
// Use our useLocalStorage hook to persist state through a page refresh. | |
// Read the recipe for this hook to learn more: usehooks.com/useLocalStorage | |
const [enabledState, setEnabledState] = useLocalStorage<boolean>( | |
"dark-mode-enabled", | |
false | |
); | |
// See if user has set a browser or OS preference for dark mode. | |
// The usePrefersDarkMode hook composes a useMedia hook (see code below). | |
const prefersDarkMode = usePrefersDarkMode(); | |
// If enabledState is defined use it, otherwise fallback to prefersDarkMode. | |
// This allows user to override OS level setting on our website. | |
const enabled = enabledState ?? prefersDarkMode; | |
// Fire off effect that add/removes dark mode class | |
useEffect( | |
() => { | |
const className = "dark-mode"; | |
const element = window.document.body; | |
if (enabled) { | |
element.classList.add(className); | |
} else { | |
element.classList.remove(className); | |
} | |
}, | |
[enabled] // Only re-call effect when value changes | |
); | |
// Return enabled state and setter | |
return [enabled, setEnabledState]; | |
} | |
// Compose our useMedia hook to detect dark mode preference. | |
// The API for useMedia looks a bit weird, but that's because ... | |
// ... it was designed to support multiple media queries and return values. | |
// Thanks to hook composition we can hide away that extra complexity! | |
// Read the recipe for useMedia to learn more: usehooks.com/useMedia | |
function usePrefersDarkMode() { | |
return useMedia<boolean>(["(prefers-color-scheme: dark)"], [true], false); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// T is a generic type for value parameter, our case this will be string | |
function useDebounce<T>(value: T, delay: number): T { | |
// State and setters for debounced value | |
const [debouncedValue, setDebouncedValue] = useState<T>(value); | |
useEffect( | |
() => { | |
// Update debounced value after delay | |
const handler = setTimeout(() => { | |
setDebouncedValue(value); | |
}, delay); | |
// Cancel the timeout if value changes (also on delay change or unmount) | |
// This is how we prevent debounced value from updating if value is changed ... | |
// .. within the delay period. Timeout gets cleared and restarted. | |
return () => { | |
clearTimeout(handler); | |
}; | |
}, | |
[value, delay] // Only re-call effect if value or delay changes | |
); | |
return debouncedValue; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const authContext = createContext(); | |
// Provider component that wraps your app and makes auth object ... | |
// ... available to any child component that calls useAuth(). | |
export function ProvideAuth({ children }) { | |
const auth = useProvideAuth(); | |
return <authContext.Provider value={auth}>{children}</authContext.Provider>; | |
} | |
// Hook for child components to get the auth object ... | |
// ... and re-render when it changes. | |
export const useAuth = () => { | |
return useContext(authContext); | |
}; | |
// Provider hook that creates auth object and handles state | |
function useProvideAuth() { | |
const [user, setUser] = useState(null); | |
// Wrap any Firebase methods we want to use making sure ... | |
// ... to save the user to state. | |
const signin = (email, password) => { | |
return firebase | |
.auth() | |
.signInWithEmailAndPassword(email, password) | |
.then((response) => { | |
setUser(response.user); | |
return response.user; | |
}); | |
}; | |
const signup = (email, password) => { | |
return firebase | |
.auth() | |
.createUserWithEmailAndPassword(email, password) | |
.then((response) => { | |
setUser(response.user); | |
return response.user; | |
}); | |
}; | |
const signout = () => { | |
return firebase | |
.auth() | |
.signOut() | |
.then(() => { | |
setUser(false); | |
}); | |
}; | |
const sendPasswordResetEmail = (email) => { | |
return firebase | |
.auth() | |
.sendPasswordResetEmail(email) | |
.then(() => { | |
return true; | |
}); | |
}; | |
const confirmPasswordReset = (code, password) => { | |
return firebase | |
.auth() | |
.confirmPasswordReset(code, password) | |
.then(() => { | |
return true; | |
}); | |
}; | |
// Subscribe to user on mount | |
// Because this sets state in the callback it will cause any ... | |
// ... component that utilizes this hook to re-render with the ... | |
// ... latest auth object. | |
useEffect(() => { | |
const unsubscribe = firebase.auth().onAuthStateChanged((user) => { | |
if (user) { | |
setUser(user); | |
} else { | |
setUser(false); | |
} | |
}); | |
// Cleanup subscription on unmount | |
return () => unsubscribe(); | |
}, []); | |
// Return the user object and auth methods | |
return { | |
user, | |
signin, | |
signup, | |
signout, | |
sendPasswordResetEmail, | |
confirmPasswordReset, | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const useLocalStorage = <T>(key: string, initialValue: T) => { | |
// State to store our value | |
// Pass initial state function to useState so logic is only executed once | |
const [storedValue, setStoredValue] = useState<T>(() => { | |
if (typeof window === "undefined") { | |
return initialValue; | |
} | |
try { | |
// Get from local storage by key | |
const item = window.localStorage.getItem(key); | |
// Parse stored json or if none return initialValue | |
return item ? JSON.parse(item) : initialValue; | |
} catch (error) { | |
// If error also return initialValue | |
console.log(error); | |
return initialValue; | |
} | |
}); | |
// Return a wrapped version of useState's setter function that ... | |
// ... persists the new value to localStorage. | |
const setValue = (value: T | ((val: T) => T)) => { | |
try { | |
// Allow value to be a function so we have same API as useState | |
const valueToStore = | |
value instanceof Function ? value(storedValue) : value; | |
// Save state | |
setStoredValue(valueToStore); | |
// Save to local storage | |
if (typeof window !== "undefined") { | |
window.localStorage.setItem(key, JSON.stringify(valueToStore)); | |
} | |
} catch (error) { | |
// A more advanced implementation would handle the error case | |
console.log(error); | |
} | |
}; | |
return [storedValue, setValue] as const; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const useMedia = <T>(queries: string[], values: T[], defaultValue: T) => { | |
// Array containing a media query list for each query | |
const mediaQueryLists = queries.map((q) => window.matchMedia(q)); | |
// Function that gets value based on matching media query | |
const getValue = () => { | |
// Get index of first media query that matches | |
const index = mediaQueryLists.findIndex((mql) => mql.matches); | |
// Return related value or defaultValue if none | |
return values?.[index] || defaultValue; | |
}; | |
// State and setter for matched value | |
const [value, setValue] = useState<T>(getValue); | |
useEffect( | |
() => { | |
// Event listener callback | |
// Note: By defining getValue outside of useEffect we ensure that it has ... | |
// ... current values of hook args (as this hook callback is created once on mount). | |
const handler = () => setValue(getValue); | |
// Set a listener for each media query with above handler as callback. | |
mediaQueryLists.forEach((mql) => mql.addListener(handler)); | |
// Remove listeners on cleanup | |
return () => | |
mediaQueryLists.forEach((mql) => mql.removeListener(handler)); | |
}, | |
[] // Empty array ensures effect is only run on mount and unmount | |
); | |
return value; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useMemo } from "react"; | |
import { | |
useParams, | |
useLocation, | |
useHistory, | |
useRouteMatch, | |
} from "react-router-dom"; | |
import queryString from "query-string"; | |
export function useRouter() { | |
const params = useParams(); | |
const location = useLocation(); | |
const history = useHistory(); | |
const match = useRouteMatch(); | |
// Return our custom router object | |
// Memoize so that a new object is only returned if something changes | |
return useMemo(() => { | |
return { | |
// For convenience add push(), replace(), pathname at top level | |
push: history.push, | |
replace: history.replace, | |
pathname: location.pathname, | |
// Merge params and parsed query string into single "query" object | |
// so that they can be used interchangeably. | |
// Example: /:topic?sort=popular -> { topic: "react", sort: "popular" } | |
query: { | |
...queryString.parse(location.search), // Convert string to object | |
...params, | |
}, | |
// Include match, location, history objects so we have | |
// access to extra React Router functionality if needed. | |
match, | |
location, | |
history, | |
}; | |
}, [params, match, location, history]); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const useToggle = (initialState: boolean = false): [boolean, any] => { | |
// Initialize the state | |
const [state, setState] = useState<boolean>(initialState); | |
// Define and memorize toggler function in case we pass down the comopnent, | |
// This function change the boolean value to it's opposite value | |
const toggle = useCallback((): void => setState(state => !state), []); | |
return [state, toggle] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment