useFirestoreQuery Composes: useMemoCompare This hook makes it super easy to subscribe to data in your Firestore database without having to worry about state management. Instead of calling Firestore's query.onSnapshot() method you simply pass a query to useFirestoreQuery() and you get back everything you need, including status , data , and error . Your component will re-render when data changes and your subscription will be automatically removed when the component unmounts. Our example even supports dependent queries where you can wait on needed data by passing a falsy value to the hook. Read through the recipe and comments below to see how it works. // Usage function ProfilePage ( { uid } ) { // Subscribe to Firestore document const { data , status , error } = useFirestoreQuery ( firestore . collection ( "profiles" ) . doc ( uid ) ) ; if ( status === "loading" ) { return "Loading..." ; } if ( status === "error" ) { return ` Error: ${ error . message } ` ; } return ( < div > < ProfileHeader avatar = { data . avatar } name = { data . name } / > < Posts posts = { data . posts } / > < / div > ) ; } // Reducer for hook state and actions const reducer = ( state , action ) => { switch ( action . type ) { case "idle" : return { status : "idle" , data : undefined , error : undefined } ; case "loading" : return { status : "loading" , data : undefined , error : undefined } ; case "success" : return { status : "success" , data : action . payload , error : undefined } ; case "error" : return { status : "error" , data : undefined , error : action . payload } ; default : throw new Error ( "invalid action" ) ; } } // Hook function useFirestoreQuery ( query ) { // Our initial state // Start with an "idle" status if query is falsy, as that means hook consumer is // waiting on required data before creating the query object. // Example: useFirestoreQuery(uid && firestore.collection("profiles").doc(uid)) const initialState = { status : query ? "loading" : "idle" , data : undefined , error : undefined } ; // Setup our state and actions const [ state , dispatch ] = useReducer ( reducer , initialState ) ; // Get cached Firestore query object with useMemoCompare (https://usehooks.com/useMemoCompare) // Needed because firestore.collection("profiles").doc(uid) will always being a new object reference // causing effect to run -> state change -> rerender -> effect runs -> etc ... // This is nicer than requiring hook consumer to always memoize query with useMemo. const queryCached = useMemoCompare ( query , prevQuery => { // Use built-in Firestore isEqual method to determine if "equal" return prevQuery && query && query . isEqual ( prevQuery ) ; } ) ; useEffect ( ( ) => { // Return early if query is falsy and reset to "idle" status in case // we're coming from "success" or "error" status due to query change. if ( ! queryCached ) { dispatch ( { type : "idle" } ) ; return ; } dispatch ( { type : "loading" } ) ; // Subscribe to query with onSnapshot // Will unsubscribe on cleanup since this returns an unsubscribe function return queryCached . onSnapshot ( response => { // Get data for collection or doc const data = response . docs ? getCollectionData ( response ) : getDocData ( response ) ; dispatch ( { type : "success" , payload : data } ) ; } , error => { dispatch ( { type : "error" , payload : error } ) ; } ) ; } , [ queryCached ] ) ; // Only run effect if queryCached changes return state ; } // Get doc data and merge doc.id function getDocData ( doc ) { return doc . exists === true ? { id : doc . id , ... doc . data ( ) } : null ; } // Get array of doc data from collection function getCollectionData ( collection ) { return collection . docs . map ( getDocData ) ; } August 11, 2020 • Suggest a change

useMemoCompare This hook is similar to useMemo, but instead of passing an array of dependencies we pass a custom compare function that receives the previous and new value. The compare function can then compare nested properties, call object methods, or anything else to determine equality. If the compare function returns true then the hook returns the old object reference.



It's worth noting that, unlike useMemo, this hook isn't meant to avoid expensive calculations. It needs to be passed a computed value so that it can compare it to the old value. Where this comes in handy is if you want to offer a library to other developers and it would be annoying to force them to memoize an object before passing it to your library. If that object is created in the component body (often the case if it's based on props) then it's going to be a new object on every render. If that object is a useEffect dependency then it's going to cause the effect to fire on every render, which can lead to problems or even an infinite loop. This hook allows you to avoid that scenario by using the old object reference instead of the new one if your custom comparison function deems them equal.



Read through the recipe and comments below. For a more practical example be sure to check out our useFirestoreQuery hook. import React , { useState , useEffect , useRef } from 'react' ; // Usage function MyComponent ( { obj } ) { const [ state , setState ] = useState ( ) ; // Use the previous obj value if the "id" property hasn't changed const objFinal = useMemoCompare ( obj , ( prev , next ) => { return prev && prev . id === next . id ; } ) ; // Here we want to fire off an effect if objFinal changes. // If we had used obj directly without the above hook and obj was technically a // new object on every render then the effect would fire on every render. // Worse yet, if our effect triggered a state change it could cause an endless loop // where effect runs -> state change causes rerender -> effect runs -> etc ... useEffect ( ( ) => { // Call a method on the object and set results to state return objFinal . someMethod ( ) . then ( ( value ) => setState ( value ) ) ; } , [ objFinal ] ) ; // So why not pass [obj.id] as the dependency array instead? useEffect ( ( ) => { // Then eslint-plugin-hooks would rightfully complain that obj is not in the // dependency array and we'd have to use eslint-disable-next-line to work around that. // It's much cleaner to just get the old object reference with our custom hook. return obj . someMethod ( ) . then ( ( value ) => setState ( value ) ) ; } , [ obj . id ] ) ; return < div > ... < / div > ; } // Hook function useMemoCompare ( next , compare ) { // Ref for storing previous value const previousRef = useRef ( ) ; const previous = previousRef . current ; // Pass previous and next value to compare function // to determine whether to consider them equal. const isEqual = compare ( previous , next ) ; // If not equal update previousRef to next value. // We only update if not equal so that this hook continues to return // the same old value if compare keeps returning true. useEffect ( ( ) => { if ( ! isEqual ) { previousRef . current = next ; } } ) ; // Finally, if equal then return the previous value return isEqual ? previous : next ; } April 08, 2020 • Suggest a change

useAsync It's generally a good practice to indicate to users the status of any async request. An example would be fetching data from an API and displaying a loading indicator before rendering the results. Another example would be a form where you want to disable the submit button when the submission is pending and then display either a success or error message when it completes.



Rather than litter your components with a bunch of useState calls to keep track of the state of an async function, you can use our custom hook which takes an async function as an input and returns the value , error , and status values we need to properly update our UI. Possible values for status prop are: "idle", "pending", "success", "error". As you'll see in the code below, our hook allows both immediate execution and delayed execution using the returned execute function. View in TypeScript import React , { useState , useEffect , useCallback } from 'react' ; // Usage function App ( ) { const { execute , status , value , error } = useAsync ( myFunction , false ) ; return ( < div > { status === 'idle' && < div > Start your journey by clicking a button < / div > } { status === 'success' && < div > { value } < / div > } { status === 'error' && < div > { error } < / div > } < button onClick = { execute } disabled = { status === 'pending' } > { status !== 'pending' ? 'Click me' : 'Loading...' } < / button > < / div > ) ; } // An async function for testing our hook. // Will be successful 50% of the time. const myFunction = ( ) => { 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 = ( asyncFunction , immediate = true ) => { const [ status , setStatus ] = useState ( 'idle' ) ; const [ value , setValue ] = useState ( null ) ; const [ error , setError ] = useState ( 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 => { setValue ( response ) ; setStatus ( 'success' ) ; } ) . catch ( error => { 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 } ; } ; January 14, 2020 • Open in CodeSandbox • Suggest a change

useRequireAuth useRouter Composes: useAuth A common need is a way to redirect the user if they are signed out and trying to view a page that should require them to be authenticated. This example shows how you can easily compose our useAuth and useRouter hooks to create a new useRequireAuth hook that does just that. Of course, this functionality could be added directly to our useAuth hook, but then we'd need to make that hook aware of our router logic. Using the power of hook composition we can keep the other two hooks as simple as possible and just utilize our new useRequireAuth when redirection is needed. import Dashboard from "./Dashboard.js" ; import Loading from "./Loading.js" ; import { useRequireAuth } from "./use-require-auth.js" ; function DashboardPage ( props ) { const auth = useRequireAuth ( ) ; // If auth is null (still fetching data) // or false (logged out, above hook will redirect) // then show loading indicator. if ( ! auth ) { return < Loading / > ; } return ( < Dashboard auth = { auth } / > ) ; } // Hook (use-require-auth.js) import { useEffect } from "react" ; import { useAuth } from "./use-auth.js" ; import { useRouter } from "./use-router.js" ; function useRequireAuth ( redirectUrl = '/signup' ) { const auth = useAuth ( ) ; const router = useRouter ( ) ; // If auth.user is false that means we're not // logged in and should redirect. useEffect ( ( ) => { if ( auth . user === false ) { router . push ( redirectUrl ) ; } } , [ auth , router ] ) ; return auth ; } November 17, 2019 • Suggest a change

useRouter If you use React Router you might have noticed they recently added a number of useful hooks, specifically useParams , useLocation , useHistory , and use useRouteMatch . But let's see if we can make it even simpler by wrapping them up into a single useRouter hook that exposes just the data and methods we need. In this recipe we show how easy it is to compose multiple hooks and combine their returned state into a single object. It makes a lot of sense for libraries like React Router to offer a selection of low-level hooks, as using only the hook you need can minimize unnecessary re-renders. That said, sometimes you want a simpler developer experience and custom hooks make that easy. import { useMemo } from "react" ; import { useParams , useLocation , useHistory , useRouteMatch } from 'react-router-dom' ; import queryString from 'query-string' ; // Usage function MyComponent ( ) { // Get the router object const router = useRouter ( ) ; // Get value from query string (?postId=123) or route param (/:postId) console . log ( router . query . postId ) ; // Get current pathname console . log ( router . pathname ) // Navigate with with router.push() return ( < button onClick = { ( e ) => router . push ( '/about' ) } > About < / button > ) ; } // Hook 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 ] ) ; } October 21, 2019 • Suggest a change

useAuth A very common scenario is you have a bunch of components that need to render different depending on whether the current user is logged in and sometimes call authentication methods like signin , signout , sendPasswordResetEmail , etc.



This is a perfect use-case for a useAuth hook that enables any component to get the current auth state and re-render if it changes. Rather than have each instance of the useAuth hook fetch the current user, the hook simply calls useContext to get the data from farther up in the component tree. The real magic happens in our <ProvideAuth> component and our useProvideAuth hook which wraps all our authentication methods (in this case we're using Firebase) and then uses React Context to make the current auth object available to all child components that call useAuth . Whew, that was a mouthfull...



Hopefully as you read through the code below it should all make sense. Another reason I like this method is it neatly abstracts away our actual auth provider (Firebase), making it super easy to change providers in the future. // Top level App component import React from "react" ; import { ProvideAuth } from "./use-auth.js" ; function App ( props ) { return ( < ProvideAuth > { /* Route components here, depending on how your app is structured. If using Next.js this would be /pages/_app.js */ } < / ProvideAuth > ) ; } // Any component that wants auth state import React from "react" ; import { useAuth } from "./use-auth.js" ; function Navbar ( props ) { // Get auth state and re-render anytime it changes const auth = useAuth ( ) ; return ( < NavbarContainer > < Logo / > < Menu > < Link to = "/about" > About < / Link > < Link to = "/contact" > Contact < / Link > { auth . user ? ( < Fragment > < Link to = "/account" > Account ( { auth . user . email } ) < / Link > < Button onClick = { ( ) => auth . signout ( ) } > Signout < / Button > < / Fragment > ) : ( < Link to = "/signin" > Signin < / Link > ) } < / Menu > < / NavbarContainer > ) ; } // Hook (use-auth.js) import React , { useState , useEffect , useContext , createContext } from "react" ; import * as firebase from "firebase/app" ; import "firebase/auth" ; // Add your Firebase credentials firebase . initializeApp ( { apiKey : "" , authDomain : "" , projectId : "" , appID : "" } ) ; 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 } ; } August 12, 2019 • Suggest a change

useEventListener If you find yourself adding a lot of event listeners using useEffect you might consider moving that logic to a custom hook. In the recipe below we create a useEventListener hook that handles checking if addEventListener is supported, adding the event listener, and removal on cleanup. See it in action in the CodeSandbox demo. import { useState , useRef , useEffect , useCallback } from 'react' ; // Usage function App ( ) { // State for storing mouse coordinates const [ coords , setCoords ] = useState ( { x : 0 , y : 0 } ) ; // Event handler utilizing useCallback ... // ... so that reference never changes. const handler = useCallback ( ( { clientX , clientY } ) => { // Update coordinates setCoords ( { x : clientX , y : clientY } ) ; } , [ setCoords ] ) ; // Add event listener using our hook useEventListener ( 'mousemove' , handler ) ; return ( < h1 > The mouse position is ( { coords . x } , { coords . y } ) < / h1 > ) ; } // Hook 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 ) ; } ; March 27, 2019 • Open in CodeSandbox • Suggest a change

useDarkMode useLocalStorage Composes: useMedia This hook handles all the stateful logic required to add a ☾ dark mode toggle to your website. It utilizes localStorage to remember the user's chosen mode, defaults to their browser or OS level setting using the prefers-color-scheme media query and manages the setting of a .dark-mode className on body to apply your styles.



This post also helps illustrate the power of hook composition. The syncing of state to localStorage is handled by our useLocalStorage hook. Detecting the user's dark mode preference is handled by our useMedia hook. Both of these hooks were created for other use-cases, but here we've composed them to create a super useful hook in relatively few lines of code. It's almost as if hooks bring the compositional power of React components to stateful logic! 🤯 View in TypeScript // Usage function App ( ) { const [ darkMode , setDarkMode ] = useDarkMode ( ) ; return ( < div > < div className = "navbar" > < Toggle darkMode = { darkMode } setDarkMode = { setDarkMode } / > < / div > < Content / > < / div > ) ; } // Hook 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 ( 'dark-mode-enabled' ) ; // 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 = typeof enabledState !== 'undefined' ? 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 ( [ '(prefers-color-scheme: dark)' ] , [ true ] , false ) ; } February 11, 2019 • Open in CodeSandbox • Suggest a change