Skip to content

Instantly share code, notes, and snippets.

@andresgcarmona
Last active January 12, 2023 19:45
Show Gist options
  • Save andresgcarmona/8a26ee05e307599cfc2e0060e35cc271 to your computer and use it in GitHub Desktop.
Save andresgcarmona/8a26ee05e307599cfc2e0060e35cc271 to your computer and use it in GitHub Desktop.
React hooks
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
);
}
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 };
};
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);
}
// 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;
}
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,
};
}
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;
}
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;
};
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]);
}
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