Skip to content

Instantly share code, notes, and snippets.

@psenger
Last active August 14, 2023 06:08
Show Gist options
  • Select an option

  • Save psenger/ada5381fa829ff984890ea96eb6aa5e5 to your computer and use it in GitHub Desktop.

Select an option

Save psenger/ada5381fa829ff984890ea96eb6aa5e5 to your computer and use it in GitHub Desktop.
[React Hooks] #ReactJs #JavaScript
// Adapted from https://betterprogramming.pub/how-to-use-media-queries-programmatically-in-react-4d6562c3bc97
// -----8<----
// BreakpointProvider
// -----8<----
import React, {
useState,
useEffect,
createContext,
useContext} from 'react';
const defaultValue = {}
const BreakpointContext = createContext(defaultValue);
const BreakpointProvider = ({children, queries}) => {
const [queryMatch, setQueryMatch] = useState({});
useEffect(() => {
const mediaQueryLists = {};
const keys = Object.keys(queries);
let isAttached = false;
const handleQueryListener = () => {
const updatedMatches = keys.reduce((acc, media) => {
acc[media] = !!(mediaQueryLists[media] && mediaQueryLists[media].matches);
return acc;
}, {})
setQueryMatch(updatedMatches)
}
if (window && window.matchMedia) {
const matches = {};
keys.forEach(media => {
if (typeof queries[media] === 'string') {
mediaQueryLists[media] = window.matchMedia(queries[media]);
matches[media] = mediaQueryLists[media].matches
} else {
matches[media] = false
}
});
setQueryMatch(matches);
isAttached = true;
keys.forEach(media => {
if(typeof queries[media] === 'string') {
mediaQueryLists[media].addListener(handleQueryListener)
}
});
}
return () => {
if(isAttached) {
keys.forEach(media => {
if(typeof queries[media] === 'string') {
mediaQueryLists[media].removeListener(handleQueryListener)
}
});
}
}
}, [queries]);
return (
<BreakpointContext.Provider value={queryMatch}>
{children}
</BreakpointContext.Provider>
)
}
function useBreakpoint() {
const context = useContext(BreakpointContext);
if(context === defaultValue) {
throw new Error('useBreakpoint must be used within BreakpointProvider');
}
return context;
}
export {useBreakpoint, BreakpointProvider};
// -----8<----
// Using BreakpointProvider
// -----8<----
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {BreakpointProvider} from './breakpoint'
const queries = {
xs: '(max-width: 320px)',
sm: '(max-width: 720px)',
md: '(max-width: 1024px)',
or: '(orientation: portrait)', // we can check orientation also
}
ReactDOM.render(
<BreakpointProvider queries={queries}>
<App />
</BreakpointProvider>, document.getElementById('root'));
// -----8<----
// Using useBreakpoint
// -----8<----
import React from 'react'
import {useBreakpoint} from './breakpoint.js'
const Usage = () => {
const breakpoints = useBreakpoint();
const matchingList = Object.keys(breakpoints).map(media => (
<li key={media}>{media} ---- {breakpoints[media] ? 'Yes' : 'No'}</li>
))
return (
<ol>
{matchingList}
</ol>
)
}
import React, {useEffect, useState} from 'react';
const useFade = (initial) => {
const [show, setShow] = useState(initial);
const [isVisible, setVisible] = useState(show);
// Update visibility when show changes
useEffect(() => {
if (show) setVisible(true);
}, [show]);
// When the animation finishes, set visibility to false
const onAnimationEnd = () => {
if (!show) setVisible(false);
};
const style = { animation: `${show ? "fadeIn" : "fadeOut"} 3s` };
// These props go on the fading DOM element
const fadeProps = {
style,
onAnimationEnd
};
return [isVisible, setShow, fadeProps];
};
function App() {
const [isVisible, setVisible, fadeProps] = useFade(false);
// some call that will launch setVisible(!isVisible)
const res = useFetch('https://someapi/api/hello', options, ()=>setVisible(!isVisible));
return (
<div className="App">
<h1 className="App-header">
React
</h1>
{isVisible && <h2 {...fadeProps}>{res.response}</h2>}
</div>
)
}
// ---------
// CSS
// ---------
//
// @keyframes fadeIn {
// 0% { opacity: 0; }
// 100% { opacity: 1; }
// }
//
// @keyframes fadeOut {
// 0% { opacity: 1; }
// 100% { opacity: 0; }
// }
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: #BF4F74;
background: papayawhip;
border: none;
border-radius: 3px;
`;
class Form extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return (
<Input
ref={this.inputRef}
placeholder="Hover to focus!"
onMouseEnter={() => {
this.inputRef.current.focus()
}}
/>
);
}
}
render(
<Form />
);
//
// You can not set state after the component is dismounted ( you will get a Warning in console )
// EG:
// Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it
// indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous
// tasks in a useEffect cleanup function.
//
// This method, attaches a boolean to the mounted reference, which can be tested for truthy.
const mounted = userRef(true);
useEffect( () => {
sleep(1000).then( () => {
if (mounted.current) {
setState(someValue);
}
})
return () => {
mounted.current = false;
};
}, [] );
import React, {useEffect, useRef} from 'react';
const useComponentDidMount = (onMountHandler) => {
useEffect(()=>{
onMountHandler();
},[]);
}
const TalkBubble = ({children}) => {
const divRef = useRef();
useComponentDidMount(()=>{
divRef.current.scrollIntoView({ behavior: "smooth" });
})
return (
<div ref={divRef}>
{children}
</div>
)
}
export default TalkBubble
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
import { useState, useEffect, useRef, useCallback } from "react";
/**
* adopted from https://blog.logrocket.com/using-react-hooks-to-create-sticky-headers/
**/
const StickyHeader = (defaultSticky = false) => {
const [isSticky, setIsSticky] = useState(defaultSticky);
const tableRef = useRef(null);
const toggleSticky = useCallback(
({ top, bottom }) => {
if (top <= 0 && bottom > 2 * 68) {
!isSticky && setIsSticky(true);
} else {
isSticky && setIsSticky(false);
}
},
[isSticky]
);
useEffect(() => {
const handleScroll = () => {
toggleSticky(tableRef.current.getBoundingClientRect());
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [toggleSticky]);
return { tableRef, isSticky };
};
export default StickyHeader;
/**
Create a custom hook with clicking inside of wrapped components.
First, create a custom hook that takes in a ref and a callback to handle the click event.
Then we make use of useEffect to append and clean up the click event.
Finally, we use useRef to create a ref for the component to be clicked and pass it to the useClickInside hook.
**/
import React, { useRef, useEffect } from 'react';
import * as ReactDom from "react-dom";
const useClickInside = (ref, callback) => {
const handleClick = e => {
if ( ref.current && ref.current.contains(e.target)) {
callback();
}
};
useEffect(()=>{
// document.addEventListener('click',handleClick); <<--- this is on the whole document, potentiall wrong.
ref.current.addEventListener('click',handleClick);
return () => {
// document.removeEventListener('click', handleClick);<<--- this is on the whole document, potentiall wrong.
ref.current.removeEventListener('click',handleClick);
}
})
}
const HitBox = ({ onClickInside }) => {
const clickRef = useRef();
useClickInside(clickRef, onClickInside);
return (<div
className="hit-box"
ref={clickRef}
style={{
border: '5px solid green',
height: 300,
width: 600,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
<p>Hit the box!</p>
</div>);
}
ReactDom.render(<HitBox onClickInside={()=>alert('hit the box')}/>,document.getElementById('root'));
import React, { useRef, useEffect } from 'react';
import * as ReactDom from "react-dom";
const useClickOutside = (ref, callback) => {
const handleClick = e => {
if ( ref.current && !ref.current.contains(e.target)) {
callback();
}
};
useEffect(()=>{
// document.addEventListener('click',handleClick); <<--- this is on the whole document, potentiall wrong.
ref.current.addEventListener('click',handleClick);
return () => {
// document.removeEventListener('click', handleClick);<<--- this is on the whole document, potentiall wrong.
ref.current.removeEventListener('click',handleClick);
}
})
}
const HitBox = ({ onClickOutside }) => {
const clickRef = useRef();
useClickOutside(clickRef, onClickOutside);
return (<div
className="hit-box"
ref={clickRef}
style={{
border: '5px dashed green',
height: 300,
width: 600,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
<p>Don't hit the box!</p>
</div>);
}
ReactDom.render(<HitBox onClickOutside={()=>alert('dont hit the box')}/>,document.getElementById('root'));
/**
* Component did mount is part of the State and Lifecycle, https://reactjs.org/docs/react-component.html#componentdidmount
* This is just another way of doing it.
* For the second argument, we simply use useEffect with an empty array,
* to execute the provided callback once as soon as the component is mounted.
*/
import React, {useEffect, useRef, useState} from 'react';
import * as ReactDom from "react-dom";
const useComponentDidMount = onMountHandler => {
useEffect(()=>{
onMountHandler();
},[]);
}
const MountComponent = props => {
useComponentDidMount(()=>{
console.log('this component has loaded')
})
return <p>check the console</p>
}
ReactDom.render(<MountComponent/>, document.getElementById('root'));
/**
* Component will un mount is part of the State and Lifecycle,
* https://reactjs.org/docs/react-component.html#componentdidmount
* This is just another way of doing it.
* For the second argument, we simply use useEffect with an empty array,
* to execute the provided callback once as soon as the component is mounted.
*/
import React, {useEffect, useRef, useState} from 'react';
import * as ReactDom from "react-dom";
const useComponentWillUnmount = onUnmountHandler => {
useEffect(()=>()=>{
onUnmountHandler();
},[]);
}
const UnmountComponent = props => {
useComponentWillUnmount(()=>{
console.log('this component has un mounted')
})
return <p>check the console</p>
}
ReactDom.render(<UnmountComponent/>, document.getElementById('root'));
/**
The useContext accepts the value provided by React.createContext
and then re-render the component whenever its value changes but
you can still optimize its performance by using memoization.
**/
// -----8<---- AuthContext.js -----8<----
import React from 'react';
const authContext = React.createContext({
auth: null,
login: () => {},
logout: () => {},
});
export default authContext;
// -----8<---- Login.js -----8<----
import React, { useContext } from 'react';
import AuthContext from './AuthContext';
const Login = () => {
const auth = useContext(AuthContext);
return (
<>
<button onClick={auth.login}>Login</button>
</>
);
};
export default Login;
// -----8<---- Logout.js -----8<----
import React, { useContext } from 'react';
import AuthContext from './AuthContext';
const Logout = () => {
const auth = useContext(AuthContext);
return (
<>
<button onClick={auth.logout}>Click To Logout</button>
</>
);
};
export default Logout;
// -----8<---- App.jsx -----8<----
import React, { useState } from 'react';
import LogIn from './Login';
import LogOut from './Logout';
import AuthContext from './AuthContext';
const App = () => {
const [auth, setAuth] = useState(false);
const login = () => {
setAuth(true);
};
const logout = () => {
setAuth(false);
};
return (
<React.Fragment>
<AuthContext.Provider
value={{ auth: auth, login: login, logout: logout }}
>
<p>{auth ? 'Hi! You are Logged In' : 'Oope! Kindly Login'}</p>
<LogIn />
<LogOut />
</AuthContext.Provider>
</React.Fragment>
);
};
export default App;
/**
* The useFetch hook can be used to implement fetch in a declarative way.
*
* First, use useState to initialize the response and error state variables.
* Then we use useEffect to asynchronously call fetch and update the state.
* Finally, we return an object that contains the response/error variables.
*/
import React, {useEffect, useState} from 'react';
import * as ReactDom from "react-dom";
const useFetch = (url, options) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
// @see https://medium.com/javascript-in-plain-english/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435
(async () => {
await fetchData();
})();
}, []);
return {response, error};
}
const FetchPerson = props => {
const res = useFetch('https://swapi.co/api/people/1', {});
if (!res.response) {
return <div>Loading...</div>
}
const person = res.response.name;
return (<div><span>{person}</span></div>)
}
ReactDom.render(<FetchPerson/>, document.getElementById('root'));
/**
* Create a custom hook with that executes a interval function.
*
* First, create a custom hook taking in a callback and a delay.
* Then use useRef to create a ref for the callback.
* Finally, useEffect to remember the latest callback and to set up the interval and clean up.
*/
import React, {useEffect, useRef, useState} from 'react';
import * as ReactDom from "react-dom";
const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay])
}
const ResourceCounter = props => {
const [resources, setResources] = useState(0);
useInterval(() => {
setResources(resources + 2)
}, 2500)
return <p>{resources}</p>
}
ReactDom.render(<ResourceCounter/>, document.getElementById('root'));
import { useMemo, useCallback } from "react";
// refer to https://reactjs.org/docs/hooks-reference.html#usememo
function MemoNizeValue() {
const [count,setCount] = useState(60);
const expensiveCount = useMemo(()=>{
return count **2;
}, [count]); // Only execute, when the count changes. Recompute.
return <></>
}
function MemoNizeFunction() {
const [count,setCount] = useState(60);
const showCount = useCallback(()=>{
// some expensive function call.
},[count]); // <<--- reuse the same function object
return <><SomeChild handler={showCount}/></>
}
/**
First, we create a custom hook that takes in a value.
Then we use the useRef hook to create a ref for the value.
Finally, we use useEffect to remember the latest value.
**/
import React, { useState, Component } from 'react';
import * as ReactDom from "react-dom";
const usePrevious = value => {
const ref = React.useRef();
React.useEffect(()=>{
ref.current = value;
});
return ref.current;
}
const MoneyCount = () => {
const [value,setValue] = React.useState(0);
const lastValue = usePrevious(value);
return (<div>
<p>Current: {value} - Previous: {lastValue}</p>
<button onClick={()=>{setValue(value+1)}}>increment</button>
</div>);
}
ReactDom.render(<MoneyCount/>,document.getElementById('root'));
/**
Create a custom hook with a callback and a delay.
Then we use the useRef hook to create a ref for the callback function.
Finally, we make use of useEffect twice.
One time for remembering the last callback and one time for setting up the timeout and cleaning up.
**/
import React, { useState, Component } from 'react';
import * as ReactDom from "react-dom";
const useTimeout = (callBack, delay) => {
const savedCallBack = React.useRef();
React.useEffect(()=>{
savedCallBack.current = callBack;
},[callBack]);
React.useEffect(()=>{
function tick() {
savedCallBack.current();
}
if ( delay !== null){
let id = setTimeout(tick,delay);
return () => clearTimeout(id);
}
},[delay]);
}
const ExampleTimerFiveSeconds = props => {
const [seconds,setSeconds] = React.useState(0);
useTimeout(()=>{
setSeconds(seconds+1 );
},5000);
return <p>{seconds}</p>
}
ReactDom.render(<ExampleTimerFiveSeconds/>,document.getElementById('root'));
/**
Create a custom hook to get the current size of the browser window.
This hook returns an object containing the window's width and height.
If executed server-side (no window object) the value of width and height will be undefined.
**/
import { useState, useEffect } from "react";
// Usage
function App() {
const size = useWindowSize();
return (
<div>
{size.width}px / {size.height}px
</div>
);
}
// Hook
function useWindowSize() {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment