Hooks
Collection of custom hooks
- If custom Hook receives a callback function that will be called inside
useEffect
:
jsx
function App() {
const [url, setUrl] = useState(null);
const { data } = useFetch({ url, onSuccess: () => console.log("Success") });
return (
<>
<div>{JSON.stringify(data)}</div>
<div>
<button onClick={() => setUrl("users")} />
<button onClick={() => setUrl("todos")} />
</div>
</>
);
}
const useCallbackRef = (callback) => {
const callbackRef = useRef(callback);
useLayoutEffect(() => {
callbackRef.current = callback;
}, [callback]);
return callbackRef;
}
export const useFetch = (options) => {
const [data, setData] = useState(null);
cosnt savedOnSuccess = useCallbackRef(options.onSuccess);
useEffect(() => {
if (options.url) {
fetch(options.url)
.then((response) => response.json())
.then((json) => {
savedOnSuccess.current();
setData(json);
});
}
}, [options.url)
}
Toggle Hook
javascript
[color, toggle] = useReducer((prev) => !prev, false);
Typescript:
typescript
import { useCallback, useState } from "react";
const App = () => {
// call the hook which returns, current value and the toggler function
const [isTextChanged, setIsTextChanged] = useToggle();
return (
<button onClick={setIsTextChanged}>
{isTextChanged ? "Toggled" : "Click to Toggle"}
</button>
);
};
// custom Hook
// parameter is the boolean, with default "false" value
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 component,
// this function change the boolean value to it's opposite value
const toggle = useCallback((): void => setState((state) => !state), []);
return [state, toggle];
};
Dark Mode
javascript
export const useDarkMode = () => {
const preferDarkQuery = "(prefers-color-scheme: dark)";
const [mode, setMode] = useState(
() =>
window.localStorage.getItem("colorMode") ||
(window.matchMedia(preferDarkQuery).matches ? "dark" : "light"),
);
useEffect(() => {
const mediaQuery = window.matchMedia(preferDarkQuery);
const handleChange = () => setMode(mediaQuery.matches ? "dark" : "light");
mediaQuery.addEventListener("change", handleChange);
return () => mediaQuery.removeEventListener(handleChange);
}, [preferDarkQuery]);
useEffect(() => {
window.localStorage.setItem("colorMode", mode);
}, [mode]);
return [mode, setMode];
};
const styles = {
dark: {
color: "white",
backgroundColor: "black",
},
light: {
color: "black",
backgroundColor: "white",
},
};
- Export a button to toggle modes:
jsx
export const Button = () => {
const [mode, setMode] = useDarkMode();
return (
<button
style={styles[mode]}
onClick={() => setMode(mode === "dark" ? "light" : "dark")}
>
{mode}
</button>
);
};
Example:
jsx
const matchDark = "(prefers-color-scheme: dark)";
const useDarkMode = () => {
const [isDark, setIsDark] = useState(
() => window.matchMedia(matchDark) && window.matchMedia(matchDark).matches,
);
useEffect(() => {
const matcher = window.matchMedia(matchDark);
const onChange = ({ matches }) => setIsDark(matches);
matcher.addEventListener(onChange);
return () => {
matcher.removeEventListener(onChange);
};
}, [setIsDark]);
return isDark;
};
const App = () => {
const theme = useDarkMode() ? themes.dark : themes.light;
return <ThemeProvider theme={theme}>...</ThemeProvider>;
};
Counter Hook
javascript
[count, increment] = useReducer((prev) => prev + 1, 0);
One-Way Boolean State
javascript
const [isEnabled, enable] = useReducer(() => true, false);
Click outside
jsx
const useClickOutside = (elRef, callback) => {
const callbackRef = useRef();
callbackRef.current = callback;
useEffect(() => {
const handleClickOutside = (e) => {
if (elRef?.current?.contains(e.target) && callback) {
callbackRef.current(e);
}
};
document.addEventListener("click", handleClickOutside, true);
return () => {
document.removeEventListener("click", handleClickOutside, true);
};
}, [callbackRef, elRef]);
};
const Menu = () => {
const menuRef = useRef(null);
const onClickOutside = () => {
console.log("Clicked outside!");
};
useClickOutside(menuRef, onClickOutside);
return <div ref={menuRef}>Menu items</div>;
};
HTTP Request
Example 1:
javascript
import { useCallback, useEffect, useState } from "react";
export const useHttp = (url = "", options) => {
const [isLoading, setIsLoading] = useState(false);
const [apiData, setApiData] = useState(null);
const [error, setError] = useState(null);
const sendRequest = useCallback(async (url, options) => {
try {
setIsLoading(true);
const response = await fetch(url, options);
if (!response.ok) throw new Error("Something went wrong");
const data = await response.json();
setApiData(data);
setError(null);
} catch (error) {
setError(error);
setApiData(null);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
if (!url) return;
const controller = new AbortController();
sendRequest(url, {
...options,
signal: controller.signal,
});
return () => {
controller.abort();
};
}, [url, options, sendRequest]);
return { isLoading, error, apiData };
};
Example 2:
jsx
// https://api.github.com/users/25prabhu10
import { useState, useEffect } from "react";
const GitHub = ({ login }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!login) return;
setLoading(true);
fetch(`https://api.github.com/users/${login}`)
.then((res) => res.json())
.then(setData)
.then(() => setLoading(false))
.catch(setError);
}, [login]);
if (loading) return <h1>Loading...</h1>;
if (error) return <pre>{JSON.stringify(error, null, 2)}</pre>;
if (!data) return null;
return (
<div>
<h1>{data.name}</h1>
<p>{data.location}</p>
<img src={data.avatar_url} alt={data.login} />
</div>
);
};
export default GitHub;