Merge 30-seconds-of-react

This commit is contained in:
Angelos Chalaris
2023-05-01 22:57:19 +03:00
79 changed files with 4682 additions and 0 deletions

29
react/snippet-template.md Normal file
View File

@ -0,0 +1,29 @@
---
title: Component Name
type: snippet
tags: [components,state,effect]
cover: image
dateModified: 2021-06-13T05:00:00-04:00
---
Explain briefly what the snippet does.
- Explain briefly how the snippet works.
- Use bullet points for your snippet's explanation.
- Try to explain everything briefly but clearly.
```jsx
const ComponentName = props => {
const [state, setState] = React.useState(null);
React.useEffect(() => {
setState(0);
});
return <div>{props}</div>;
}
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<ComponentName />
);
```

View File

@ -0,0 +1,86 @@
---
title: Collapsible accordion
type: snippet
tags: [components,children,state]
cover: beach-pineapple
dateModified: 2021-10-13T19:29:39+02:00
---
Renders an accordion menu with multiple collapsible content elements.
- Define an `AccordionItem` component, that renders a `<button>`. The button updates the component and notifies its parent via the `handleClick` callback.
- Use the `isCollapsed` prop in `AccordionItem` to determine its appearance and set its `className`.
- Define an `Accordion` component. Use the `useState()` hook to initialize the value of the `bindIndex` state variable to `defaultIndex`.
- Filter `children` to remove unnecessary nodes except for `AccordionItem` by identifying the function's name.
- Use `Array.prototype.map()` on the collected nodes to render the individual collapsible elements.
- Define `changeItem`, which will be executed when clicking an `AccordionItem`'s `<button>`.
- `changeItem` executes the passed callback, `onItemClick`, and updates `bindIndex` based on the clicked element.
```css
.accordion-item.collapsed {
display: none;
}
.accordion-item.expanded {
display: block;
}
.accordion-button {
display: block;
width: 100%;
}
```
```jsx
const AccordionItem = ({ label, isCollapsed, handleClick, children }) => {
return (
<>
<button className="accordion-button" onClick={handleClick}>
{label}
</button>
<div
className={`accordion-item ${isCollapsed ? 'collapsed' : 'expanded'}`}
aria-expanded={isCollapsed}
>
{children}
</div>
</>
);
};
const Accordion = ({ defaultIndex, onItemClick, children }) => {
const [bindIndex, setBindIndex] = React.useState(defaultIndex);
const changeItem = itemIndex => {
if (typeof onItemClick === 'function') onItemClick(itemIndex);
if (itemIndex !== bindIndex) setBindIndex(itemIndex);
};
const items = children.filter(item => item.type.name === 'AccordionItem');
return (
<>
{items.map(({ props }) => (
<AccordionItem
isCollapsed={bindIndex !== props.index}
label={props.label}
handleClick={() => changeItem(props.index)}
children={props.children}
/>
))}
</>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Accordion defaultIndex="1" onItemClick={console.log}>
<AccordionItem label="A" index="1">
Lorem ipsum
</AccordionItem>
<AccordionItem label="B" index="2">
Dolor sit amet
</AccordionItem>
</Accordion>
);
```

107
react/snippets/alert.md Normal file
View File

@ -0,0 +1,107 @@
---
title: Closable alert
type: snippet
tags: [components,state,effect]
cover: flower-portrait-1
dateModified: 2021-01-07T23:57:13+02:00
---
Renders an alert component with `type` prop.
- Use the `useState()` hook to create the `isShown` and `isLeaving` state variables and set both to `false` initially.
- Define `timeoutId` to keep the timer instance for clearing on component unmount.
- Use the `useEffect()` hook to update the value of `isShown` to `true` and clear the interval by using `timeoutId` when the component is unmounted.
- Define a `closeAlert` function to set the component as removed from the DOM by displaying a fading out animation and set `isShown` to `false` via `setTimeout()`.
```css
@keyframes leave {
0% { opacity: 1 }
100% { opacity: 0 }
}
.alert {
padding: 0.75rem 0.5rem;
margin-bottom: 0.5rem;
text-align: left;
padding-right: 40px;
border-radius: 4px;
font-size: 16px;
position: relative;
}
.alert.warning {
color: #856404;
background-color: #fff3cd;
border-color: #ffeeba;
}
.alert.error {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}
.alert.leaving {
animation: leave 0.5s forwards;
}
.alert .close {
position: absolute;
top: 0;
right: 0;
padding: 0 0.75rem;
color: #333;
border: 0;
height: 100%;
cursor: pointer;
background: none;
font-weight: 600;
font-size: 16px;
}
.alert .close::after {
content: 'x';
}
```
```jsx
const Alert = ({ isDefaultShown = false, timeout = 250, type, message }) => {
const [isShown, setIsShown] = React.useState(isDefaultShown);
const [isLeaving, setIsLeaving] = React.useState(false);
let timeoutId = null;
React.useEffect(() => {
setIsShown(true);
return () => {
clearTimeout(timeoutId);
};
}, [isDefaultShown, timeout, timeoutId]);
const closeAlert = () => {
setIsLeaving(true);
timeoutId = setTimeout(() => {
setIsLeaving(false);
setIsShown(false);
}, timeout);
};
return (
isShown && (
<div
className={`alert ${type} ${isLeaving ? 'leaving' : ''}`}
role="alert"
>
<button className="close" onClick={closeAlert} />
{message}
</div>
)
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Alert type="info" message="This is info" />
);
```

View File

@ -0,0 +1,41 @@
---
title: Automatic text linking
type: snippet
tags: [components,fragment,regexp]
author: chalarangelo
cover: red-petals
dateModified: 2020-11-03T20:42:15+02:00
---
Renders a string as plaintext, with URLs converted to appropriate link elements.
- Use `String.prototype.split()` and `String.prototype.match()` with a regular expression to find URLs in a string.
- Return matched URLs rendered as `<a>` elements, dealing with missing protocol prefixes if necessary.
- Render the rest of the string as plaintext.
```jsx
const AutoLink = ({ text }) => {
const delimiter = /((?:https?:\/\/)?(?:(?:[a-z0-9]?(?:[a-z0-9\-]{1,61}[a-z0-9])?\.[^\.|\s])+[a-z\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\d{1,5})*[a-z0-9.,_\/~#&=;%+?\-\\(\\)]*)/gi;
return (
<>
{text.split(delimiter).map(word => {
const match = word.match(delimiter);
if (match) {
const url = match[0];
return (
<a href={url.startsWith('http') ? url : `http://${url}`}>{url}</a>
);
}
return word;
})}
</>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<AutoLink text="foo bar baz http://example.org bar" />
);
```

26
react/snippets/callto.md Normal file
View File

@ -0,0 +1,26 @@
---
title: Callable telephone link
type: snippet
tags: [components]
author: chalarangelo
unlisted: true
cover: rabbit-call
dateModified: 2021-01-04T12:32:47+02:00
---
Renders a link formatted to call a phone number (`tel:` link).
- Use `phone` to create a `<a>` element with an appropriate `href` attribute.
- Render the link with `children` as its content.
```jsx
const Callto = ({ phone, children }) => {
return <a href={`tel:${phone}`}>{children}</a>;
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Callto phone="+302101234567">Call me!</Callto>
);
```

View File

@ -0,0 +1,67 @@
---
title: Carousel
type: snippet
tags: [components,children,state,effect]
cover: shell-focus
dateModified: 2020-11-03T20:42:15+02:00
---
Renders a carousel component.
- Use the `useState()` hook to create the `active` state variable and give it a value of `0` (index of the first item).
- Use the `useEffect()` hook to update the value of `active` to the index of the next item, using `setTimeout()`.
- Compute the `className` for each carousel item while mapping over them and applying it accordingly.
- Render the carousel items using `React.cloneElement()` and pass down `...rest` along with the computed `className`.
```css
.carousel {
position: relative;
}
.carousel-item {
position: absolute;
visibility: hidden;
}
.carousel-item.visible {
visibility: visible;
}
```
```jsx
const Carousel = ({ carouselItems, ...rest }) => {
const [active, setActive] = React.useState(0);
let scrollInterval = null;
React.useEffect(() => {
scrollInterval = setTimeout(() => {
setActive((active + 1) % carouselItems.length);
}, 2000);
return () => clearTimeout(scrollInterval);
});
return (
<div className="carousel">
{carouselItems.map((item, index) => {
const activeClass = active === index ? ' visible' : '';
return React.cloneElement(item, {
...rest,
className: `carousel-item${activeClass}`
});
})}
</div>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Carousel
carouselItems={[
<div>carousel item 1</div>,
<div>carousel item 2</div>,
<div>carousel item 3</div>
]}
/>
);
```

View File

@ -0,0 +1,61 @@
---
title: Collapsible content
type: snippet
tags: [components,children,state]
cover: washed-ashore
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a component with collapsible content.
- Use the `useState()` hook to create the `isCollapsed` state variable. Give it an initial value of `collapsed`.
- Use the `<button>` to change the component's `isCollapsed` state and the content of the component, passed down via `children`.
- Use `isCollapsed` to determine the appearance of the content and apply the appropriate `className`.
- Update the value of the `aria-expanded` attribute based on `isCollapsed` to make the component accessible.
```css
.collapse-button {
display: block;
width: 100%;
}
.collapse-content.collapsed {
display: none;
}
.collapsed-content.expanded {
display: block;
}
```
```jsx
const Collapse = ({ collapsed, children }) => {
const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
return (
<>
<button
className="collapse-button"
onClick={() => setIsCollapsed(!isCollapsed)}
>
{isCollapsed ? 'Show' : 'Hide'} content
</button>
<div
className={`collapse-content ${isCollapsed ? 'collapsed' : 'expanded'}`}
aria-expanded={isCollapsed}
>
{children}
</div>
</>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Collapse>
<h1>This is a collapse</h1>
<p>Hello world!</p>
</Collapse>
);
```

View File

@ -0,0 +1,44 @@
---
title: Controlled input field
type: snippet
tags: [components,input]
cover: digital-nomad-5
dateModified: 2020-11-03T21:08:39+02:00
---
Renders a controlled `<input>` element that uses a callback function to inform its parent about value updates.
- Use the `value` passed down from the parent as the controlled input field's value.
- Use the `onChange` event to fire the `onValueChange` callback and send the new value to the parent.
- The parent must update the input field's `value` prop in order for its value to change on user input.
```jsx
const ControlledInput = ({ value, onValueChange, ...rest }) => {
return (
<input
value={value}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
/>
);
};
```
```jsx
const Form = () => {
const [value, setValue] = React.useState('');
return (
<ControlledInput
type="text"
placeholder="Insert some text here..."
value={value}
onValueChange={setValue}
/>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Form />
);
```

View File

@ -0,0 +1,66 @@
---
title: Countdown timer
type: snippet
tags: [components,state]
cover: sea-view-2
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a countdown timer that prints a message when it reaches zero.
- Use the `useState()` hook to create a state variable to hold the time value. Initialize it from the props and destructure it into its components.
- Use the `useState()` hook to create the `paused` and `over` state variables, used to prevent the timer from ticking if it's paused or the time has run out.
- Create a method `tick`, that updates the time values based on the current value (i.e. decreasing the time by one second).
- Create a method `reset`, that resets all state variables to their initial states.
- Use the the `useEffect()` hook to call the `tick` method every second via the use of `setInterval()` and use `clearInterval()` to clean up when the component is unmounted.
- Use `String.prototype.padStart()` to pad each part of the time array to two characters to create the visual representation of the timer.
```jsx
const CountDown = ({ hours = 0, minutes = 0, seconds = 0 }) => {
const [paused, setPaused] = React.useState(false);
const [over, setOver] = React.useState(false);
const [[h, m, s], setTime] = React.useState([hours, minutes, seconds]);
const tick = () => {
if (paused || over) return;
if (h === 0 && m === 0 && s === 0) setOver(true);
else if (m === 0 && s === 0) {
setTime([h - 1, 59, 59]);
} else if (s == 0) {
setTime([h, m - 1, 59]);
} else {
setTime([h, m, s - 1]);
}
};
const reset = () => {
setTime([parseInt(hours), parseInt(minutes), parseInt(seconds)]);
setPaused(false);
setOver(false);
};
React.useEffect(() => {
const timerID = setInterval(() => tick(), 1000);
return () => clearInterval(timerID);
});
return (
<div>
<p>{`${h.toString().padStart(2, '0')}:${m
.toString()
.padStart(2, '0')}:${s.toString().padStart(2, '0')}`}</p>
<div>{over ? "Time's up!" : ''}</div>
<button onClick={() => setPaused(!paused)}>
{paused ? 'Resume' : 'Pause'}
</button>
<button onClick={() => reset()}>Restart</button>
</div>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<CountDown hours={1} minutes={45} />
);
```

View File

@ -0,0 +1,29 @@
---
title: Data list
type: snippet
tags: [components]
cover: interior-14
dateModified: 2020-11-03T21:26:34+02:00
---
Renders a list of elements from an array of primitives.
- Use the value of the `isOrdered` prop to conditionally render an `<ol>` or a `<ul>` list.
- Use `Array.prototype.map()` to render every item in `data` as a `<li>` element with an appropriate `key`.
```jsx
const DataList = ({ isOrdered = false, data }) => {
const list = data.map((val, i) => <li key={`${i}_${val}`}>{val}</li>);
return isOrdered ? <ol>{list}</ol> : <ul>{list}</ul>;
};
```
```jsx
const names = ['John', 'Paul', 'Mary'];
ReactDOM.createRoot(document.getElementById('root')).render(
<>
<DataList data={names} />
<DataList data={names} isOrdered />
</>
);
```

View File

@ -0,0 +1,42 @@
---
title: Data table
type: snippet
tags: [components]
cover: armchair
dateModified: 2020-11-03T21:26:34+02:00
---
Renders a table with rows dynamically created from an array of primitives.
- Render a `<table>` element with two columns (`ID` and `Value`).
- Use `Array.prototype.map()` to render every item in `data` as a `<tr>` element with an appropriate `key`.
```jsx
const DataTable = ({ data }) => {
return (
<table>
<thead>
<tr>
<th>ID</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{data.map((val, i) => (
<tr key={`${i}_${val}`}>
<td>{i}</td>
<td>{val}</td>
</tr>
))}
</tbody>
</table>
);
};
```
```jsx
const people = ['John', 'Jesse'];
ReactDOM.createRoot(document.getElementById('root')).render(
<DataTable data={people} />
);
```

107
react/snippets/file-drop.md Normal file
View File

@ -0,0 +1,107 @@
---
title: File drag and drop area
type: snippet
tags: [components,input,state,effect,event]
author: chalarangelo
cover: man-red-sunset
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a file drag and drop component for a single file.
- Create a ref, called `dropRef` and bind it to the component's wrapper.
- Use the `useState()` hook to create the `drag` and `filename` variables. Initialize them to `false` and `''` respectively.
- The variables `dragCounter` and `drag` are used to determine if a file is being dragged, while `filename` is used to store the dropped file's name.
- Create the `handleDrag`, `handleDragIn`, `handleDragOut` and `handleDrop` methods to handle drag and drop functionality.
- `handleDrag` prevents the browser from opening the dragged file. `handleDragIn` and `handleDragOut` handle the dragged file entering and exiting the component. `handleDrop` handles the file being dropped and passes it to `onDrop`.
- Use the `useEffect()` hook to handle each of the drag and drop events using the previously created methods.
```css
.filedrop {
min-height: 120px;
border: 3px solid #d3d3d3;
text-align: center;
font-size: 24px;
padding: 32px;
border-radius: 4px;
}
.filedrop.drag {
border: 3px dashed #1e90ff;
}
.filedrop.ready {
border: 3px solid #32cd32;
}
```
```jsx
const FileDrop = ({ onDrop }) => {
const [drag, setDrag] = React.useState(false);
const [filename, setFilename] = React.useState('');
let dropRef = React.createRef();
let dragCounter = 0;
const handleDrag = e => {
e.preventDefault();
e.stopPropagation();
};
const handleDragIn = e => {
e.preventDefault();
e.stopPropagation();
dragCounter++;
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) setDrag(true);
};
const handleDragOut = e => {
e.preventDefault();
e.stopPropagation();
dragCounter--;
if (dragCounter === 0) setDrag(false);
};
const handleDrop = e => {
e.preventDefault();
e.stopPropagation();
setDrag(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
onDrop(e.dataTransfer.files[0]);
setFilename(e.dataTransfer.files[0].name);
e.dataTransfer.clearData();
dragCounter = 0;
}
};
React.useEffect(() => {
let div = dropRef.current;
div.addEventListener('dragenter', handleDragIn);
div.addEventListener('dragleave', handleDragOut);
div.addEventListener('dragover', handleDrag);
div.addEventListener('drop', handleDrop);
return () => {
div.removeEventListener('dragenter', handleDragIn);
div.removeEventListener('dragleave', handleDragOut);
div.removeEventListener('dragover', handleDrag);
div.removeEventListener('drop', handleDrop);
};
});
return (
<div
ref={dropRef}
className={
drag ? 'filedrop drag' : filename ? 'filedrop ready' : 'filedrop'
}
>
{filename && !drag ? <div>{filename}</div> : <div>Drop a file here!</div>}
</div>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<FileDrop onDrop={console.log} />
);
```

View File

@ -0,0 +1,79 @@
---
title: Lazy-loading image
type: snippet
tags: [components,effect,state]
cover: strawberries
author: chalarangelo
dateModified: 2022-07-29T05:00:00-04:00
---
Renders an image that supports lazy loading.
- Use the `useState()` hook to create a stateful value that indicates if the image has been loaded.
- Use the `useEffect()` hook to check if the `HTMLImageElement.prototype` contains `'loading'`, effectively checking if lazy loading is supported natively. If not, create a new `IntersectionObserver` and use `IntersectionObserver.observer()` to observer the `<img>` element. Use the `return` value of the hook to clean up when the component unmounts.
- Use the `useCallback()` hook to memoize a callback function for the `IntersectionObserver`. This callback will update the `isLoaded` state variable and use `IntersectionObserver.disconnect()` to disconnect the `IntersectionObserver` instance.
- Use the `useRef()` hook to create two refs. One will hold the `<img>` element and the other the `IntersectionObserver` instance, if necessary.
- Finally, render the `<img>` element with the given attributes. Apply `loading='lazy'` to make it load lazily, if necessary. Use `isLoaded` to determine the value of the `src` attribute.
```jsx
const LazyLoadImage = ({
alt,
src,
className,
loadInitially = false,
observerOptions = { root: null, rootMargin: '200px 0px' },
...props
}) => {
const observerRef = React.useRef(null);
const imgRef = React.useRef(null);
const [isLoaded, setIsLoaded] = React.useState(loadInitially);
const observerCallback = React.useCallback(
entries => {
if (entries[0].isIntersecting) {
observerRef.current.disconnect();
setIsLoaded(true);
}
},
[observerRef]
);
React.useEffect(() => {
if (loadInitially) return;
if ('loading' in HTMLImageElement.prototype) {
setIsLoaded(true);
return;
}
observerRef.current = new IntersectionObserver(
observerCallback,
observerOptions
);
observerRef.current.observe(imgRef.current);
return () => {
observerRef.current.disconnect();
};
}, []);
return (
<img
alt={alt}
src={isLoaded ? src : ''}
ref={imgRef}
className={className}
loading={loadInitially ? undefined : 'lazy'}
{...props}
/>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<LazyLoadImage
src="https://picsum.photos/id/1080/600/600"
alt="Strawberries"
/>
);
```

View File

@ -0,0 +1,46 @@
---
title: Textarea with character limit
type: snippet
tags: [components,state,callback,event]
cover: flower-portrait-2
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a textarea component with a character limit.
- Use the `useState()` hook to create the `content` state variable. Set its value to that of `value` prop, trimmed down to `limit` characters.
- Create a method `setFormattedContent`, which trims the content down to `limit` characters and memoize it, using the `useCallback()` hook.
- Bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of the fired event.
```jsx
const LimitedTextarea = ({ rows, cols, value, limit }) => {
const [content, setContent] = React.useState(value.slice(0, limit));
const setFormattedContent = React.useCallback(
text => {
setContent(text.slice(0, limit));
},
[limit, setContent]
);
return (
<>
<textarea
rows={rows}
cols={cols}
onChange={event => setFormattedContent(event.target.value)}
value={content}
/>
<p>
{content.length}/{limit}
</p>
</>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<LimitedTextarea limit={32} value="Hello!" />
);
```

View File

@ -0,0 +1,63 @@
---
title: Textarea with word limit
type: snippet
tags: [components,input,state,callback,effect,event]
cover: painters-desk
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a textarea component with a word limit.
- Use the `useState()` hook to create a state variable, containing `content` and `wordCount`. Use the `value` prop and `0` as the initial values respectively.
- Use the `useCallback()` hooks to create a memoized function, `setFormattedContent`, that uses `String.prototype.split()` to turn the input into an array of words.
- Check if the result of applying `Array.prototype.filter()` combined with `Boolean` has a `length` longer than `limit`. If it does, trim the input. Otherwise return the raw input, updating state accordingly in both cases.
- Use the `useEffect()` hook to call the `setFormattedContent` method on the value of the `content` state variable during the initial render.
- Bind the `onChange` event of the `<textarea>` to call `setFormattedContent` with the value of `event.target.value`.
```jsx
const LimitedWordTextarea = ({ rows, cols, value, limit }) => {
const [{ content, wordCount }, setContent] = React.useState({
content: value,
wordCount: 0
});
const setFormattedContent = React.useCallback(
text => {
let words = text.split(' ').filter(Boolean);
if (words.length > limit) {
setContent({
content: words.slice(0, limit).join(' '),
wordCount: limit
});
} else {
setContent({ content: text, wordCount: words.length });
}
},
[limit, setContent]
);
React.useEffect(() => {
setFormattedContent(content);
}, []);
return (
<>
<textarea
rows={rows}
cols={cols}
onChange={event => setFormattedContent(event.target.value)}
value={content}
/>
<p>
{wordCount}/{limit}
</p>
</>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<LimitedWordTextarea limit={5} value="Hello there!" />
);
```

70
react/snippets/loader.md Normal file
View File

@ -0,0 +1,70 @@
---
title: Spinning loader
type: snippet
tags: [components]
cover: godray-computer-mug
dateModified: 2020-11-16T15:17:26+02:00
---
Renders a spinning loader component.
- Render an SVG, whose `height` and `width` are determined by the `size` prop.
- Use CSS to animate the SVG, creating a spinning animation.
```css
.loader {
animation: rotate 2s linear infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
.loader circle {
animation: dash 1.5s ease-in-out infinite;
}
@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
```
```jsx
const Loader = ({ size }) => {
return (
<svg
className="loader"
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
</svg>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Loader size={24} />
);
```

32
react/snippets/mailto.md Normal file
View File

@ -0,0 +1,32 @@
---
title: Email link
type: snippet
tags: [components]
author: chalarangelo
cover: digital-nomad-4
dateModified: 2020-11-16T15:17:26+02:00
---
Renders a link formatted to send an email (`mailto:` link).
- Use the `email`, `subject` and `body` props to create a `<a>` element with an appropriate `href` attribute.
- Use `encodeURIcomponent` to safely encode the `subject` and `body` into the link URL.
- Render the link with `children` as its content.
```jsx
const Mailto = ({ email, subject = '', body = '', children }) => {
let params = subject || body ? '?' : '';
if (subject) params += `subject=${encodeURIComponent(subject)}`;
if (body) params += `${subject ? '&' : ''}body=${encodeURIComponent(body)}`;
return <a href={`mailto:${email}${params}`}>{children}</a>;
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Mailto email="foo@bar.baz" subject="Hello & Welcome" body="Hello world!">
Mail me!
</Mailto>
);
```

View File

@ -0,0 +1,56 @@
---
title: Object table view
type: snippet
tags: [components,array,object]
cover: waves-from-above-2
dateModified: 2020-09-06T14:20:45+03:00
---
Renders a table with rows dynamically created from an array of objects and a list of property names.
- Use `Object.keys()`, `Array.prototype.filter()`, `Array.prototype.includes()` and `Array.prototype.reduce()` to produce a `filteredData` array, containing all objects with the keys specified in `propertyNames`.
- Render a `<table>` element with a set of columns equal to the amount of values in `propertyNames`.
- Use `Array.prototype.map()` to render each value in the `propertyNames` array as a `<th>` element.
- Use `Array.prototype.map()` to render each object in the `filteredData` array as a `<tr>` element, containing a `<td>` for each key in the object.
- **Note:** This component does not work with nested objects and will break if there are nested objects inside any of the properties specified in `propertyNames`.
```jsx
const MappedTable = ({ data, propertyNames }) => {
let filteredData = data.map(v =>
Object.keys(v)
.filter(k => propertyNames.includes(k))
.reduce((acc, key) => ((acc[key] = v[key]), acc), {})
);
return (
<table>
<thead>
<tr>
{propertyNames.map(val => (
<th key={`h_${val}`}>{val}</th>
))}
</tr>
</thead>
<tbody>
{filteredData.map((val, i) => (
<tr key={`i_${i}`}>
{propertyNames.map(p => (
<td key={`i_${i}_${p}`}>{val[p]}</td>
))}
</tr>
))}
</tbody>
</table>
);
};
```
```jsx
const people = [
{ name: 'John', surname: 'Smith', age: 42 },
{ name: 'Adam', surname: 'Smith', gender: 'male' }
];
const propertyNames = ['name', 'surname', 'age'];
ReactDOM.createRoot(document.getElementById('root')).render(
<MappedTable data={people} propertyNames={propertyNames} />
);
```

157
react/snippets/modal.md Normal file
View File

@ -0,0 +1,157 @@
---
title: Modal dialog
type: snippet
tags: [components,effect]
cover: yellow-white-mug-2
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a Modal component, controllable through events.
- Define a `keydownHandler` that handles all keyboard events and calls `onClose` when the `Esc` key is pressed.
- Use the `useEffect()` hook to add or remove the `keydown` event listener to the `Document`, calling `keydownHandler` for every event.
- Add a styled `<span>` element that acts as a close button, calling `onClose` when clicked.
- Use the `isVisible` prop passed down from the parent to determine if the modal should be displayed or not.
- To use the component, import `Modal` only once and then display it by passing a boolean value to the `isVisible` attribute.
```css
.modal {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.25);
animation-name: appear;
animation-duration: 300ms;
}
.modal-dialog {
width: 100%;
max-width: 550px;
background: white;
position: relative;
margin: 0 20px;
max-height: calc(100vh - 40px);
text-align: left;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: slide-in;
animation-duration: 0.5s;
}
.modal-header,
.modal-footer {
display: flex;
align-items: center;
padding: 1rem;
}
.modal-header {
border-bottom: 1px solid #dbdbdb;
justify-content: space-between;
}
.modal-footer {
border-top: 1px solid #dbdbdb;
justify-content: flex-end;
}
.modal-close {
cursor: pointer;
padding: 1rem;
margin: -1rem -1rem -1rem auto;
}
.modal-body {
overflow: auto;
}
.modal-content {
padding: 1rem;
}
@keyframes appear {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-in {
from {
transform: translateY(-150px);
}
to {
transform: translateY(0);
}
}
```
```jsx
const Modal = ({ isVisible = false, title, content, footer, onClose }) => {
const keydownHandler = ({ key }) => {
switch (key) {
case 'Escape':
onClose();
break;
default:
}
};
React.useEffect(() => {
document.addEventListener('keydown', keydownHandler);
return () => document.removeEventListener('keydown', keydownHandler);
});
return !isVisible ? null : (
<div className="modal" onClick={onClose}>
<div className="modal-dialog" onClick={e => e.stopPropagation()}>
<div className="modal-header">
<h3 className="modal-title">{title}</h3>
<span className="modal-close" onClick={onClose}>
&times;
</span>
</div>
<div className="modal-body">
<div className="modal-content">{content}</div>
</div>
{footer && <div className="modal-footer">{footer}</div>}
</div>
</div>
);
};
```
```jsx
const App = () => {
const [isModal, setModal] = React.useState(false);
return (
<>
<button onClick={() => setModal(true)}>Click Here</button>
<Modal
isVisible={isModal}
title="Modal Title"
content={<p>Add your content here</p>}
footer={<button>Cancel</button>}
onClose={() => setModal(false)}
/>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,58 @@
---
title: Stateful checkbox with multiple selection
type: snippet
tags: [components,input,state,array]
cover: violin
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a checkbox list that uses a callback function to pass its selected value/values to the parent component.
- Use the `useState()` hook to create the `data` state variable and use the `options` prop to initialize its value.
- Create a `toggle` function that uses the spread operator (`...`) and `Array.prototype.splice()` to update the `data` state variable and call the `onChange` callback with any `checked` options.
- Use `Array.prototype.map()` to map the `data` state variable to individual `<input type="checkbox">` elements. Wrap each one in a `<label>`, binding the `onClick` handler to the `toggle` function.
```jsx
const MultiselectCheckbox = ({ options, onChange }) => {
const [data, setData] = React.useState(options);
const toggle = index => {
const newData = [...data];
newData.splice(index, 1, {
label: data[index].label,
checked: !data[index].checked
});
setData(newData);
onChange(newData.filter(x => x.checked));
};
return (
<>
{data.map((item, index) => (
<label key={item.label}>
<input
readOnly
type="checkbox"
checked={item.checked || false}
onClick={() => toggle(index)}
/>
{item.label}
</label>
))}
</>
);
};
```
```jsx
const options = [{ label: 'Item One' }, { label: 'Item Two' }];
ReactDOM.createRoot(document.getElementById('root')).render(
<MultiselectCheckbox
options={options}
onChange={data => {
console.log(data);
}}
/>
);
```

View File

@ -0,0 +1,31 @@
---
title: Show/hide password toggle
type: snippet
tags: [components,input,state]
author: chalarangelo
cover: thread
dateModified: 2020-11-25T20:46:35+02:00
---
Renders a password input field with a reveal button.
- Use the `useState()` hook to create the `shown` state variable and set its value to `false`.
- When the `<button>` is clicked, execute `setShown`, toggling the `type` of the `<input>` between `'text'` and `'password'`.
```jsx
const PasswordRevealer = ({ value }) => {
const [shown, setShown] = React.useState(false);
return (
<>
<input type={shown ? 'text' : 'password'} value={value} />
<button onClick={() => setShown(!shown)}>Show/Hide</button>
</>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<PasswordRevealer />
);
```

View File

@ -0,0 +1,110 @@
---
title: Button with ripple effect
type: snippet
tags: [components,state,effect]
author: chalarangelo
cover: mountain-lake-cottage
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a button that animates a ripple effect when clicked.
- Use the `useState()` hook to create the `coords` and `isRippling` state variables. These are used for the pointer's coordinates and the animation state of the button respectively.
- Use a `useEffect()` hook to change the value of `isRippling` every time the `coords` state variable changes, starting the animation.
- Use `setTimeout()` in the previous hook to clear the animation after it's done playing.
- Use a `useEffect()` hook to reset `coords` whenever the `isRippling` state variable is `false`.
- Handle the `onClick` event by updating the `coords` state variable and calling the passed callback.
```css
.ripple-button {
border-radius: 4px;
border: none;
margin: 8px;
padding: 14px 24px;
background: #1976d2;
color: #fff;
overflow: hidden;
position: relative;
cursor: pointer;
}
.ripple-button > .ripple {
width: 20px;
height: 20px;
position: absolute;
background: #63a4ff;
display: block;
content: "";
border-radius: 9999px;
opacity: 1;
animation: 0.9s ease 1 forwards ripple-effect;
}
@keyframes ripple-effect {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(10);
opacity: 0.375;
}
100% {
transform: scale(35);
opacity: 0;
}
}
.ripple-button > .content {
position: relative;
z-index: 2;
}
```
```jsx
const RippleButton = ({ children, onClick }) => {
const [coords, setCoords] = React.useState({ x: -1, y: -1 });
const [isRippling, setIsRippling] = React.useState(false);
React.useEffect(() => {
if (coords.x !== -1 && coords.y !== -1) {
setIsRippling(true);
setTimeout(() => setIsRippling(false), 300);
} else setIsRippling(false);
}, [coords]);
React.useEffect(() => {
if (!isRippling) setCoords({ x: -1, y: -1 });
}, [isRippling]);
return (
<button
className="ripple-button"
onClick={e => {
const rect = e.target.getBoundingClientRect();
setCoords({ x: e.clientX - rect.left, y: e.clientY - rect.top });
onClick && onClick(e);
}}
>
{isRippling ? (
<span
className="ripple"
style={{
left: coords.x,
top: coords.y
}}
/>
) : (
''
)}
<span className="content">{children}</span>
</button>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<RippleButton onClick={e => console.log(e)}>Click me</RippleButton>
);
```

48
react/snippets/select.md Normal file
View File

@ -0,0 +1,48 @@
---
title: Uncontrolled select element
type: snippet
tags: [components,input]
cover: down-the-stream
dateModified: 2020-11-25T20:46:35+02:00
---
Renders an uncontrolled `<select>` element that uses a callback function to pass its value to the parent component.
- Use the the `selectedValue` prop as the `defaultValue` of the `<select>` element to set its initial value..
- Use the `onChange` event to fire the `onValueChange` callback and send the new value to the parent.
- Use `Array.prototype.map()` on the `values` array to create an `<option>` element for each passed value.
- Each item in `values` must be a 2-element array, where the first element is the `value` of the item and the second one is the displayed text for it.
```jsx
const Select = ({ values, onValueChange, selectedValue, ...rest }) => {
return (
<select
defaultValue={selectedValue}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
>
{values.map(([value, text]) => (
<option key={value} value={value}>
{text}
</option>
))}
</select>
);
};
```
```jsx
const choices = [
['grapefruit', 'Grapefruit'],
['lime', 'Lime'],
['coconut', 'Coconut'],
['mango', 'Mango'],
];
ReactDOM.createRoot(document.getElementById('root')).render(
<Select
values={choices}
selectedValue="lime"
onValueChange={val => console.log(val)}
/>
);
```

40
react/snippets/slider.md Normal file
View File

@ -0,0 +1,40 @@
---
title: Uncontrolled range input
type: snippet
tags: [components,input]
cover: solitude-beach
dateModified: 2020-11-25T20:46:35+02:00
---
Renders an uncontrolled range input element that uses a callback function to pass its value to the parent component.
- Set the `type` of the `<input>` element to `"range"` to create a slider.
- Use the `defaultValue` passed down from the parent as the uncontrolled input field's initial value.
- Use the `onChange` event to fire the `onValueChange` callback and send the new value to the parent.
```jsx
const Slider = ({
min = 0,
max = 100,
defaultValue,
onValueChange,
...rest
}) => {
return (
<input
type="range"
min={min}
max={max}
defaultValue={defaultValue}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
/>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Slider onValueChange={val => console.log(val)} />
);
```

View File

@ -0,0 +1,65 @@
---
title: Star rating
type: snippet
tags: [components,children,input,state]
cover: lake-church
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a star rating component.
- Define a `Star` component. It renders each individual star with the appropriate appearance, based on the parent component's state.
- Define a `StarRating` component. Use the `useState()` hook to define the `rating` and `selection` state variables with the appropriate initial values.
- Create a method, `hoverOver`, that updates `selected` according to the provided `event`, using the .`data-star-id` attribute of the event's target or resets it to `0` if called with a `null` argument.
- Use `Array.from()` to create an array of `5` elements and `Array.prototype.map()` to create individual `<Star>` components.
- Handle the `onMouseOver` and `onMouseLeave` events of the wrapping element using `hoverOver`. Handle the `onClick` event using `setRating`.
```css
.star {
color: #ff9933;
cursor: pointer;
}
```
```jsx
const Star = ({ marked, starId }) => {
return (
<span data-star-id={starId} className="star" role="button">
{marked ? '\u2605' : '\u2606'}
</span>
);
};
const StarRating = ({ value }) => {
const [rating, setRating] = React.useState(parseInt(value) || 0);
const [selection, setSelection] = React.useState(0);
const hoverOver = event => {
let val = 0;
if (event && event.target && event.target.getAttribute('data-star-id'))
val = event.target.getAttribute('data-star-id');
setSelection(val);
};
return (
<div
onMouseOut={() => hoverOver(null)}
onClick={e => setRating(e.target.getAttribute('data-star-id') || rating)}
onMouseOver={hoverOver}
>
{Array.from({ length: 5 }, (v, i) => (
<Star
starId={i + 1}
key={`star_${i + 1}`}
marked={selection ? selection >= i + 1 : rating >= i + 1}
/>
))}
</div>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<StarRating value={2} />
);
```

95
react/snippets/tabs.md Normal file
View File

@ -0,0 +1,95 @@
---
title: Tabs
type: snippet
tags: [components,state,children]
cover: by-the-lighthouse
dateModified: 2021-10-13T19:29:39+02:00
---
Renders a tabbed menu and view component.
- Define a `Tabs` component. Use the `useState()` hook to initialize the value of the `bindIndex` state variable to `defaultIndex`.
- Define a `TabItem` component and filter `children` passed to the `Tabs` component to remove unnecessary nodes except for `TabItem` by identifying the function's name.
- Define `changeTab`, which will be executed when clicking a `<button>` from the menu.
- `changeTab` executes the passed callback, `onTabClick`, and updates `bindIndex` based on the clicked element.
- Use `Array.prototype.map()` on the collected nodes to render the menu and view of the tabs.
- Use the value of `bindIndex` to determine the active tab and apply the correct `className`.
```css
.tab-menu > button {
cursor: pointer;
padding: 8px 16px;
border: 0;
border-bottom: 2px solid transparent;
background: none;
}
.tab-menu > button.focus {
border-bottom: 2px solid #007bef;
}
.tab-menu > button:hover {
border-bottom: 2px solid #007bef;
}
.tab-content {
display: none;
}
.tab-content.selected {
display: block;
}
```
```jsx
const TabItem = props => <div {...props} />;
const Tabs = ({ defaultIndex = 0, onTabClick, children }) => {
const [bindIndex, setBindIndex] = React.useState(defaultIndex);
const changeTab = newIndex => {
if (typeof onTabClick === 'function') onTabClick(newIndex);
setBindIndex(newIndex);
};
const items = children.filter(item => item.type.name === 'TabItem');
return (
<div className="wrapper">
<div className="tab-menu">
{items.map(({ props: { index, label } }) => (
<button
key={`tab-btn-${index}`}
onClick={() => changeTab(index)}
className={bindIndex === index ? 'focus' : ''}
>
{label}
</button>
))}
</div>
<div className="tab-view">
{items.map(({ props }) => (
<div
{...props}
className={`tab-content ${
bindIndex === props.index ? 'selected' : ''
}`}
key={`tab-content-${props.index}`}
/>
))}
</div>
</div>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Tabs defaultIndex="1" onTabClick={console.log}>
<TabItem label="A" index="1">
Lorem ipsum
</TabItem>
<TabItem label="B" index="2">
Dolor sit amet
</TabItem>
</Tabs>
);
```

122
react/snippets/tag-input.md Normal file
View File

@ -0,0 +1,122 @@
---
title: Tag input field
type: snippet
tags: [components,input,state]
cover: interior-4
dateModified: 2020-11-25T21:12:16+02:00
---
Renders a tag input field.
- Define a `TagInput` component and use the `useState()` hook to initialize an array from `tags`.
- Use `Array.prototype.map()` on the collected nodes to render the list of tags.
- Define the `addTagData` method, which will be executed when pressing the `Enter` key.
- The `addTagData` method calls `setTagData` to add the new tag using the spread (`...`) operator to prepend the existing tags and add the new tag at the end of the `tagData` array.
- Define the `removeTagData` method, which will be executed on clicking the delete icon in the tag.
- Use `Array.prototype.filter()` in the `removeTagData` method to remove the tag using its `index` to filter it out from the `tagData` array.
```css
.tag-input {
display: flex;
flex-wrap: wrap;
min-height: 48px;
padding: 0 8px;
border: 1px solid #d6d8da;
border-radius: 6px;
}
.tag-input input {
flex: 1;
border: none;
height: 46px;
font-size: 14px;
padding: 4px 0 0;
}
.tag-input input:focus {
outline: transparent;
}
.tags {
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 8px 0 0;
}
.tag {
width: auto;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
padding: 0 8px;
font-size: 14px;
list-style: none;
border-radius: 6px;
margin: 0 8px 8px 0;
background: #0052cc;
}
.tag-title {
margin-top: 3px;
}
.tag-close-icon {
display: block;
width: 16px;
height: 16px;
line-height: 16px;
text-align: center;
font-size: 14px;
margin-left: 8px;
color: #0052cc;
border-radius: 50%;
background: #fff;
cursor: pointer;
}
```
```jsx
const TagInput = ({ tags }) => {
const [tagData, setTagData] = React.useState(tags);
const removeTagData = indexToRemove => {
setTagData([...tagData.filter((_, index) => index !== indexToRemove)]);
};
const addTagData = event => {
if (event.target.value !== '') {
setTagData([...tagData, event.target.value]);
event.target.value = '';
}
};
return (
<div className="tag-input">
<ul className="tags">
{tagData.map((tag, index) => (
<li key={index} className="tag">
<span className="tag-title">{tag}</span>
<span
className="tag-close-icon"
onClick={() => removeTagData(index)}
>
x
</span>
</li>
))}
</ul>
<input
type="text"
onKeyUp={event => (event.key === 'Enter' ? addTagData(event) : null)}
placeholder="Press enter to add a tag"
/>
</div>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<TagInput tags={['Nodejs', 'MongoDB']} />
);
```

View File

@ -0,0 +1,41 @@
---
title: Uncontrolled textarea element
type: snippet
tags: [components,input]
cover: volcano-sunset
dateModified: 2020-11-25T20:46:35+02:00
---
Renders an uncontrolled `<textarea>` element that uses a callback function to pass its value to the parent component.
- Use the `defaultValue` passed down from the parent as the uncontrolled input field's initial value.
- Use the `onChange` event to fire the `onValueChange` callback and send the new value to the parent.
```jsx
const TextArea = ({
cols = 20,
rows = 2,
defaultValue,
onValueChange,
...rest
}) => {
return (
<textarea
cols={cols}
rows={rows}
defaultValue={defaultValue}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
/>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<TextArea
placeholder="Insert some text here..."
onValueChange={val => console.log(val)}
/>
);
```

50
react/snippets/toggle.md Normal file
View File

@ -0,0 +1,50 @@
---
title: Toggle
type: snippet
tags: [components,state]
cover: cows
dateModified: 2020-11-16T16:50:57+02:00
---
Renders a toggle component.
- Use the `useState()` hook to initialize the `isToggledOn` state variable to `defaultToggled`.
- Render an `<input>` and bind its `onClick` event to update the `isToggledOn` state variable, applying the appropriate `className` to the wrapping `<label>`.
```css
.toggle input[type="checkbox"] {
display: none;
}
.toggle.on {
background-color: green;
}
.toggle.off {
background-color: red;
}
```
```jsx
const Toggle = ({ defaultToggled = false }) => {
const [isToggleOn, setIsToggleOn] = React.useState(defaultToggled);
return (
<label className={isToggleOn ? 'toggle on' : 'toggle off'}>
<input
type="checkbox"
checked={isToggleOn}
onChange={() => setIsToggleOn(!isToggleOn)}
/>
{isToggleOn ? 'ON' : 'OFF'}
</label>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Toggle />
);
```

72
react/snippets/tooltip.md Normal file
View File

@ -0,0 +1,72 @@
---
title: Tooltip
type: snippet
tags: [components,state,children]
cover: dark-leaves-6
dateModified: 2020-11-16T15:17:26+02:00
---
Renders a tooltip component.
- Use the `useState()` hook to create the `show` variable and initialize it to `false`.
- Render a container element that contains the tooltip element and the `children` passed to the component.
- Handle the `onMouseEnter` and `onMouseLeave` methods, by altering the value of the `show` variable, toggling the `className` of the tooltip.
```css
.tooltip-container {
position: relative;
}
.tooltip-box {
position: absolute;
background: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 5px;
border-radius: 5px;
top: calc(100% + 5px);
display: none;
}
.tooltip-box.visible {
display: block;
}
.tooltip-arrow {
position: absolute;
top: -10px;
left: 50%;
border-width: 5px;
border-style: solid;
border-color: transparent transparent rgba(0, 0, 0, 0.7) transparent;
}
```
```jsx
const Tooltip = ({ children, text, ...rest }) => {
const [show, setShow] = React.useState(false);
return (
<div className="tooltip-container">
<div className={show ? 'tooltip-box visible' : 'tooltip-box'}>
{text}
<span className="tooltip-arrow" />
</div>
<div
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
{...rest}
>
{children}
</div>
</div>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<Tooltip text="Simple tooltip">
<button>Hover me!</button>
</Tooltip>
);
```

141
react/snippets/tree-view.md Normal file
View File

@ -0,0 +1,141 @@
---
title: Expandable object tree view
type: snippet
tags: [components,object,state,recursion]
author: chalarangelo
cover: digital-nomad-9
dateModified: 2020-11-16T16:50:57+02:00
---
Renders a tree view of a JSON object or array with collapsible content.
- Use the value of the `toggled` prop to determine the initial state of the content (collapsed/expanded).
- Use the `useState()` hook to create the `isToggled` state variable and give it the value of the `toggled` prop initially.
- Render a `<span>` element and bind its `onClick` event to alter the component's `isToggled` state.
- Determine the appearance of the component, based on `isParentToggled`, `isToggled`, `name` and checking for `Array.isArray()` on `data`.
- For each child in `data`, determine if it is an object or array and recursively render a sub-tree or a text element with the appropriate style.
```css
.tree-element {
margin: 0 0 0 4px;
position: relative;
}
.tree-element.is-child {
margin-left: 16px;
}
div.tree-element::before {
content: '';
position: absolute;
top: 24px;
left: 1px;
height: calc(100% - 48px);
border-left: 1px solid gray;
}
p.tree-element {
margin-left: 16px;
}
.toggler {
position: absolute;
top: 10px;
left: 0px;
width: 0;
height: 0;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 5px solid gray;
cursor: pointer;
}
.toggler.closed {
transform: rotate(90deg);
}
.collapsed {
display: none;
}
```
```jsx
const TreeView = ({
data,
toggled = true,
name = null,
isLast = true,
isChildElement = false,
isParentToggled = true
}) => {
const [isToggled, setIsToggled] = React.useState(toggled);
const isDataArray = Array.isArray(data);
return (
<div
className={`tree-element ${isParentToggled && 'collapsed'} ${
isChildElement && 'is-child'
}`}
>
<span
className={isToggled ? 'toggler' : 'toggler closed'}
onClick={() => setIsToggled(!isToggled)}
/>
{name ? <strong>&nbsp;&nbsp;{name}: </strong> : <span>&nbsp;&nbsp;</span>}
{isDataArray ? '[' : '{'}
{!isToggled && '...'}
{Object.keys(data).map((v, i, a) =>
typeof data[v] === 'object' ? (
<TreeView
key={`${name}-${v}-${i}`}
data={data[v]}
isLast={i === a.length - 1}
name={isDataArray ? null : v}
isChildElement
isParentToggled={isParentToggled && isToggled}
/>
) : (
<p
key={`${name}-${v}-${i}`}
className={isToggled ? 'tree-element' : 'tree-element collapsed'}
>
{isDataArray ? '' : <strong>{v}: </strong>}
{data[v]}
{i === a.length - 1 ? '' : ','}
</p>
)
)}
{isDataArray ? ']' : '}'}
{!isLast ? ',' : ''}
</div>
);
};
```
```jsx
const data = {
lorem: {
ipsum: 'dolor sit',
amet: {
consectetur: 'adipiscing',
elit: [
'duis',
'vitae',
{
semper: 'orci'
},
{
est: 'sed ornare'
},
'etiam',
['laoreet', 'tincidunt'],
['vestibulum', 'ante']
]
},
ipsum: 'primis'
}
};
ReactDOM.createRoot(document.getElementById('root')).render(
<TreeView data={data} name="data" />
);
```

View File

@ -0,0 +1,34 @@
---
title: Uncontrolled input field
type: snippet
tags: [components,input]
cover: digital-nomad-15
dateModified: 2020-11-03T21:08:39+02:00
---
Renders an uncontrolled `<input>` element that uses a callback function to inform its parent about value updates.
- Use the `defaultValue` passed down from the parent as the uncontrolled input field's initial value.
- Use the `onChange` event to fire the `onValueChange` callback and send the new value to the parent.
```jsx
const UncontrolledInput = ({ defaultValue, onValueChange, ...rest }) => {
return (
<input
defaultValue={defaultValue}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
/>
);
};
```
```jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<UncontrolledInput
type="text"
placeholder="Insert some text here..."
onValueChange={console.log}
/>
);
```

View File

@ -0,0 +1,79 @@
---
title: React useAsync hook
type: snippet
tags: [hooks,state,reducer]
cover: oven-paddle
dateModified: 2021-01-07T23:57:13+02:00
---
Handles asynchronous calls.
- Create a custom hook that takes a handler function, `fn`.
- Define a reducer function and an initial state for the custom hook's state.
- Use the `useReducer()` hook to initialize the `state` variable and the `dispatch` function.
- Define an asynchronous `run` function that will run the provided callback, `fn`, while using `dispatch` to update `state` as necessary.
- Return an object containing the properties of `state` (`value`, `error` and `loading`) and the `run` function.
```jsx
const useAsync = fn => {
const initialState = { loading: false, error: null, value: null };
const stateReducer = (_, action) => {
switch (action.type) {
case 'start':
return { loading: true, error: null, value: null };
case 'finish':
return { loading: false, error: null, value: action.value };
case 'error':
return { loading: false, error: action.error, value: null };
}
};
const [state, dispatch] = React.useReducer(stateReducer, initialState);
const run = async (args = null) => {
try {
dispatch({ type: 'start' });
const value = await fn(args);
dispatch({ type: 'finish', value });
} catch (error) {
dispatch({ type: 'error', error });
}
};
return { ...state, run };
};
```
```jsx
const RandomImage = props => {
const imgFetch = useAsync(url =>
fetch(url).then(response => response.json())
);
return (
<div>
<button
onClick={() => imgFetch.run('https://dog.ceo/api/breeds/image/random')}
disabled={imgFetch.isLoading}
>
Load image
</button>
<br />
{imgFetch.loading && <div>Loading...</div>}
{imgFetch.error && <div>Error {imgFetch.error}</div>}
{imgFetch.value && (
<img
src={imgFetch.value.message}
alt="avatar"
width={400}
height="auto"
/>
)}
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<RandomImage />
);
```

View File

@ -0,0 +1,66 @@
---
title: React useBodyScrollLock hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: folded-map
dateModified: 2021-09-02T05:00:00-04:00
---
Enables body scroll locking.
- Use the `useLayoutEffect()` with an empty array as the second argument to execute the provided callback only once when the component is mounted.
- Use `Window.getComputedStyle()` to get the `overflow` value of the `body` element and store it in a variable.
- Replace the `overflow` value of the `body` element with `'hidden'` and restore it to its original value when unmounting.
```jsx
const useBodyScrollLock = () => {
React.useLayoutEffect(() => {
const originalStyle = window.getComputedStyle(document.body).overflow;
document.body.style.overflow = 'hidden';
return () => (document.body.style.overflow = originalStyle);
}, []);
};
```
```jsx
const Modal = ({ onClose }) => {
useBodyScrollLock();
return (
<div
style={{
zIndex: 100, background: 'rgba(0,0,0,0.25)', display: 'flex',
justifyContent: 'center', alignItems: 'center'
}}
>
<p
style={{ padding: 8, borderRadius: 8, background: '#fff' }}
onClick={onClose}
>
Scroll locked! <br /> Click me to unlock
</p>
</div>
);
};
const MyApp = () => {
const [modalOpen, setModalOpen] = React.useState(false);
return (
<div
style={{
height: '400vh', textAlign: 'center', paddingTop: 100,
background: 'linear-gradient(to bottom, #1fa2ff, #12d8fa, #a6ffcb)'
}}
>
<button onClick={() => setModalOpen(true)}>Open modal</button>
{modalOpen && <Modal onClose={() => setModalOpen(false)} />}
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,57 @@
---
title: React useClickInside hook
type: snippet
tags: [hooks,effect,event]
author: chalarangelo
cover: digital-nomad-10
dateModified: 2020-11-16T14:17:53+02:00
---
Handles the event of clicking inside the wrapped component.
- Create a custom hook that takes a `ref` and a `callback` to handle the `'click'` event.
- Use the `useEffect()` hook to append and clean up the `click` event.
- Use the `useRef()` hook to create a `ref` for your click component and pass it to the `useClickInside` hook.
```jsx
const useClickInside = (ref, callback) => {
const handleClick = e => {
if (ref.current && ref.current.contains(e.target)) {
callback();
}
};
React.useEffect(() => {
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
});
};
```
```jsx
const ClickBox = ({ onClickInside }) => {
const clickRef = React.useRef();
useClickInside(clickRef, onClickInside);
return (
<div
className="click-box"
ref={clickRef}
style={{
border: '2px dashed orangered',
height: 200,
width: 400,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
<p>Click inside this element</p>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<ClickBox onClickInside={() => alert('click inside')} />
);
```

View File

@ -0,0 +1,57 @@
---
title: React useClickOutside hook
type: snippet
tags: [hooks,effect,event]
author: chalarangelo
cover: clutter
dateModified: 2020-11-16T14:17:53+02:00
---
Handles the event of clicking outside of the wrapped component.
- Create a custom hook that takes a `ref` and a `callback` to handle the `click` event.
- Use the `useEffect()` hook to append and clean up the `click` event.
- Use the `useRef()` hook to create a `ref` for your click component and pass it to the `useClickOutside` hook.
```jsx
const useClickOutside = (ref, callback) => {
const handleClick = e => {
if (ref.current && !ref.current.contains(e.target)) {
callback();
}
};
React.useEffect(() => {
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
});
};
```
```jsx
const ClickBox = ({ onClickOutside }) => {
const clickRef = React.useRef();
useClickOutside(clickRef, onClickOutside);
return (
<div
className="click-box"
ref={clickRef}
style={{
border: '2px dashed orangered',
height: 200,
width: 400,
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
<p>Click out of this element</p>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<ClickBox onClickOutside={() => alert('click outside')} />
);
```

View File

@ -0,0 +1,33 @@
---
title: React useComponentDidMount hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: highlands
dateModified: 2021-10-13T19:29:39+02:00
---
Executes a callback immediately after a component is mounted.
- Use the `useEffect()` hook with an empty array as the second argument. This will execute the provided callback only once when the component is mounted.
- Behaves like the `componentDidMount()` lifecycle method of class components.
```jsx
const useComponentDidMount = onMountHandler => {
React.useEffect(() => {
onMountHandler();
}, []);
};
```
```jsx
const Mounter = () => {
useComponentDidMount(() => console.log('Component did mount'));
return <div>Check the console!</div>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Mounter />
);
```

View File

@ -0,0 +1,53 @@
---
title: React useComponentDidUpdate hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: flower-portrait-10
dateModified: 2021-11-09T05:00:00-04:00
---
Executes a callback immediately after a component is updated.
- Use the `useRef()` hook to create a variable, `mounted`, that tracks if the component has been mounted.
- Use the `useEffect()` hook to set the value of `mounted` to `true` the first time the hook is executed.
- Run the provided `callback` on subsequent hook executions.
- Providing a dependency array for the second argument, `condition`, will only execute the hook if any of the dependencies change.
- Behaves like the `componentDidUpdate()` lifecycle method of class components.
```jsx
const useComponentDidUpdate = (callback, condition) => {
const mounted = React.useRef(false);
React.useEffect(() => {
if (mounted.current) callback();
else mounted.current = true;
}, condition);
};
```
```jsx
const App = () => {
const [value, setValue] = React.useState(0);
const [otherValue, setOtherValue] = React.useState(1);
useComponentDidUpdate(() => {
console.log(`Current value is ${value}.`);
}, [value]);
return (
<>
<p>
Value: {value}, other value: {otherValue}
</p>
<button onClick={() => setValue(value + 1)}>Increment value</button>
<button onClick={() => setOtherValue(otherValue + 1)}>
Increment other value
</button>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,36 @@
---
title: React useComponentWillUnmount hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: rocky-beach-3
dateModified: 2021-10-13T19:29:39+02:00
---
Executes a callback immediately before a component is unmounted and destroyed.
- Use the `useEffect()` hook with an empty array as the second argument. Return the provided callback to be executed only once before cleanup.
- Behaves like the `componentWillUnmount()` lifecycle method of class components.
```jsx
const useComponentWillUnmount = onUnmountHandler => {
React.useEffect(
() => () => {
onUnmountHandler();
},
[]
);
};
```
```jsx
const Unmounter = () => {
useComponentWillUnmount(() => console.log('Component will unmount'));
return <div>Check the console!</div>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Unmounter />
);
```

View File

@ -0,0 +1,66 @@
---
title: React useCopyToClipboard hook
type: snippet
tags: [hooks,effect,state,callback]
author: chalarangelo
cover: antelope
dateModified: 2020-11-16T14:17:53+02:00
---
Copies the given text to the clipboard.
- Use the [copyToClipboard](/js/s/copy-to-clipboard/) snippet to copy the text to clipboard.
- Use the `useState()` hook to initialize the `copied` variable.
- Use the `useCallback()` hook to create a callback for the `copyToClipboard` method.
- Use the `useEffect()` hook to reset the `copied` state variable if the `text` changes.
- Return the `copied` state variable and the `copy` callback.
```jsx
const useCopyToClipboard = text => {
const copyToClipboard = str => {
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
const selected =
document.getSelection().rangeCount > 0
? document.getSelection().getRangeAt(0)
: false;
el.select();
const success = document.execCommand('copy');
document.body.removeChild(el);
if (selected) {
document.getSelection().removeAllRanges();
document.getSelection().addRange(selected);
}
return success;
};
const [copied, setCopied] = React.useState(false);
const copy = React.useCallback(() => {
if (!copied) setCopied(copyToClipboard(text));
}, [text]);
React.useEffect(() => () => setCopied(false), [text]);
return [copied, copy];
};
```
```jsx
const TextCopy = props => {
const [copied, copy] = useCopyToClipboard('Lorem ipsum');
return (
<div>
<button onClick={copy}>Click to copy</button>
<span>{copied && 'Copied!'}</span>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<TextCopy />
);
```

View File

@ -0,0 +1,55 @@
---
title: React useDebounce hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: blue-bird
dateModified: 2021-01-04T16:48:43+02:00
---
Debounces the given value.
- Create a custom hook that takes a `value` and a `delay`.
- Use the `useState()` hook to store the debounced value.
- Use the `useEffect()` hook to update the debounced value every time `value` is updated.
- Use `setTimeout()` to create a timeout that delays invoking the setter of the previous state variable by `delay` ms.
- Use `clearTimeout()` to clean up when dismounting the component.
- This is particularly useful when dealing with user input.
```jsx
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = React.useState(value);
React.useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value]);
return debouncedValue;
};
```
```jsx
const Counter = () => {
const [value, setValue] = React.useState(0);
const lastValue = useDebounce(value, 500);
return (
<div>
<p>
Current: {value} - Debounced: {lastValue}
</p>
<button onClick={() => setValue(value + 1)}>Increment</button>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Counter />
);
```

View File

@ -0,0 +1,40 @@
---
title: React useDefault hook
type: snippet
tags: [hooks,state]
author: chalarangelo
cover: flower-portrait-8
dateModified: 2021-10-23T05:00:00-04:00
---
Creates a stateful value with a default fallback if it's `null` or `undefined`, and a function to update it.
- Use the `useState()` hook to create stateful value.
- Check if the value is either `null` or `undefined`.
- Return the `defaultState` if it is, otherwise return the actual `value` state, alongside the `setValue` function.
```jsx
const useDefault = (defaultState, initialState) => {
const [value, setValue] = React.useState(initialState);
const isValueEmpty = value === undefined || value === null;
return [isValueEmpty ? defaultState : value, setValue];
};
```
```jsx
const UserCard = () => {
const [user, setUser] = useDefault({ name: 'Adam' }, { name: 'John' });
return (
<>
<div>User: {user.name}</div>
<input onChange={e => setUser({ name: e.target.value })} />
<button onClick={() => setUser(null)}>Clear</button>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<UserCard />
);
```

View File

@ -0,0 +1,69 @@
---
title: React useDelayedState hook
type: snippet
tags: [hooks,state,effect]
cover: city-view
dateModified: 2021-12-15T05:00:00-04:00
---
Delays creating a stateful value until some condition is met.
- Use the `useState()` hook to create a stateful value containing the actual `state` and a boolean, `loaded`.
- Use the `useEffect()` hook to update the stateful value if the `condition` or `loaded` changes.
- Create a function, `updateState`, that only updates the `state` value if `loaded` is truthy.
```jsx
const useDelayedState = (initialState, condition) => {
const [{ state, loaded }, setState] = React.useState({
state: null,
loaded: false,
});
React.useEffect(() => {
if (!loaded && condition) setState({ state: initialState, loaded: true });
}, [condition, loaded]);
const updateState = newState => {
if (!loaded) return;
setState({ state: newState, loaded });
};
return [state, updateState];
};
```
```jsx
const App = () => {
const [branches, setBranches] = React.useState([]);
const [selectedBranch, setSelectedBranch] = useDelayedState(
branches[0],
branches.length
);
React.useEffect(() => {
const handle = setTimeout(() => {
setBranches(['master', 'staging', 'test', 'dev']);
}, 2000);
return () => {
handle && clearTimeout(handle);
};
}, []);
return (
<div>
<p>Selected branch: {selectedBranch}</p>
<select onChange={e => setSelectedBranch(e.target.value)}>
{branches.map(branch => (
<option key={branch} value={branch}>
{branch}
</option>
))}
</select>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,40 @@
---
title: React useEffectOnce hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: pop-of-green
dateModified: 2021-11-16T05:00:00-04:00
---
Runs a callback at most once when a condition becomes true.
- Use the `useRef()` hook to create a variable, `hasRunOnce`, to keep track of the execution status of the effect.
- Use the `useEffect()` that runs only when the `when` condition changes.
- Check if `when` is `true` and the effect has not executed before. If both are `true`, run `callback` and set `hasRunOnce` to `true`.
```jsx
const useEffectOnce = (callback, when) => {
const hasRunOnce = React.useRef(false);
React.useEffect(() => {
if (when && !hasRunOnce.current) {
callback();
hasRunOnce.current = true;
}
}, [when]);
};
```
```jsx
const App = () => {
const [clicked, setClicked] = React.useState(false);
useEffectOnce(() => {
console.log('mounted');
}, clicked);
return <button onClick={() => setClicked(true)}>Click me</button>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,46 @@
---
title: React useError hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: baloons-field
dateModified: 2021-09-30T05:00:00-04:00
---
Creates an error dispatcher.
- Use the `useState()` hook to create a state variable that holds the error.
- Use the `useEffect()` hook to `throw` the error whenever it's truthy.
- Use the `useCallback()` hook to update the state and return the cached function.
```jsx
const useError = err => {
const [error, setError] = React.useState(err);
React.useEffect(() => {
if (error) throw error;
}, [error]);
const dispatchError = React.useCallback(err => {
setError(err);
}, []);
return dispatchError;
};
```
```jsx
const ErrorButton = () => {
const dispatchError = useError();
const clickHandler = () => {
dispatchError(new Error('Error!'));
};
return <button onClick={clickHandler}>Throw error</button>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<ErrorButton />
);
```

View File

@ -0,0 +1,58 @@
---
title: React useEventListener hook
type: snippet
tags: [hooks,effect,event]
author: chalarangelo
cover: beach-riders
dateModified: 2021-09-01T05:00:00-04:00
---
Adds an event listener for the specified event type on the given element.
- Use the `useRef()` hook to create a ref that will hold the `handler`.
- Use the `useEffect()` hook to update the value of the `savedHandler` ref any time the `handler` changes.
- Use the `useEffect()` hook to add an event listener to the given element and clean up when unmounting.
- Omit the last argument, `el`, to use the `Window` by default.
```jsx
const useEventListener = (type, handler, el = window) => {
const savedHandler = React.useRef();
React.useEffect(() => {
savedHandler.current = handler;
}, [handler]);
React.useEffect(() => {
const listener = e => savedHandler.current(e);
el.addEventListener(type, listener);
return () => {
el.removeEventListener(type, listener);
};
}, [type, el]);
};
```
```jsx
const MyApp = () => {
const [coords, setCoords] = React.useState({ x: 0, y: 0 });
const updateCoords = React.useCallback(
({ clientX, clientY }) => {
setCoords({ x: clientX, y: clientY });
},
[setCoords]
);
useEventListener('mousemove', updateCoords);
return (
<p>Mouse coordinates: {coords.x}, {coords.y}</p>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,64 @@
---
title: React useFetch hook
type: snippet
tags: [hooks,effect,state]
author: chalarangelo
cover: coworking-space
dateModified: 2022-05-01T12:50:38+02:00
---
Implements `fetch()` in a declarative manner.
- Create a custom hook that takes a `url` and `options`.
- Use the `useState()` hook to initialize the `response`, `error` and `abort` state variables.
- Use the `useEffect()` hook to asynchronously call `fetch()` and update the state variables accordingly.
- Create and use an `AbortController` to allow aborting the request. Use it to cancel the request when the component unmounts.
- Return an object containing the `response`, `error` and `abort` state variables.
```jsx
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
const [abort, setAbort] = React.useState(() => {});
React.useEffect(() => {
const fetchData = async () => {
try {
const abortController = new AbortController();
const signal = abortController.signal;
setAbort(abortController.abort);
const res = await fetch(url, {...options, signal});
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
return () => {
abort();
}
}, []);
return { response, error, abort };
};
```
```jsx
const ImageFetch = props => {
const res = useFetch('https://dog.ceo/api/breeds/image/random', {});
if (!res.response) {
return <div>Loading...</div>;
}
const imageUrl = res.response.message;
return (
<div>
<img src={imageUrl} alt="avatar" width={400} height="auto" />
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<ImageFetch />
);
```

View File

@ -0,0 +1,52 @@
---
title: React useForm hook
type: snippet
tags: [hooks,state]
cover: cave-view
dateModified: 2021-09-17T05:00:00-04:00
---
Creates a stateful value from the fields in a form.
- Use the `useState()` hook to create a state variable for the values in the form.
- Create a function that will be called with an appropriate event by a form field and update the state variable accordingly.
```jsx
const useForm = initialValues => {
const [values, setValues] = React.useState(initialValues);
return [
values,
e => {
setValues({
...values,
[e.target.name]: e.target.value
});
}
];
};
```
```jsx
const Form = () => {
const initialState = { email: '', password: '' };
const [values, setValues] = useForm(initialState);
const handleSubmit = e => {
e.preventDefault();
console.log(values);
};
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={setValues} />
<input type="password" name="password" onChange={setValues} />
<button type="submit">Submit</button>
</form>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Form />
);
```

View File

@ -0,0 +1,49 @@
---
title: React useGetSet hook
type: snippet
tags: [hooks,state]
author: chalarangelo
cover: interior-12
dateModified: 2021-10-27T05:00:00-04:00
---
Creates a stateful value, returning a getter and a setter function.
- Use the `useRef()` hook to create a ref that holds the stateful value, initializing it with `initialState`.
- Use the `useReducer()` hook that creates a new object every time it's updated and return its dispatch.
- Use the `useMemo()` hook to memoize a pair of functions. The first one will return the current value of the `state` ref and the second one will update it and force a re-render.
```jsx
const useGetSet = initialState => {
const state = React.useRef(initialState);
const [, update] = React.useReducer(() => ({}));
return React.useMemo(
() => [
() => state.current,
newState => {
state.current = newState;
update();
},
],
[]
);
};
```
```jsx
const Counter = () => {
const [getCount, setCount] = useGetSet(0);
const onClick = () => {
setTimeout(() => {
setCount(getCount() + 1);
}, 1_000);
};
return <button onClick={onClick}>Count: {getCount()}</button>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Counter />
);
```

View File

@ -0,0 +1,63 @@
---
title: React useHash hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: book-chair
dateModified: 2021-10-02T05:00:00-04:00
---
Tracks the browser's location hash value, and allows changing it.
- Use the `useState()` hook to lazily get the `hash` property of the `Location` object.
- Use the `useCallback()` hook to create a handler that updates the state.
- Use the `useEffect()` hook to add a listener for the `'hashchange'` event when mounting and clean it up when unmounting.
- Use the `useCallback()` hook to create a function that updates the `hash` property of the `Location` object with the given value.
```jsx
const useHash = () => {
const [hash, setHash] = React.useState(() => window.location.hash);
const hashChangeHandler = React.useCallback(() => {
setHash(window.location.hash);
}, []);
React.useEffect(() => {
window.addEventListener('hashchange', hashChangeHandler);
return () => {
window.removeEventListener('hashchange', hashChangeHandler);
};
}, []);
const updateHash = React.useCallback(
newHash => {
if (newHash !== hash) window.location.hash = newHash;
},
[hash]
);
return [hash, updateHash];
};
```
```jsx
const MyApp = () => {
const [hash, setHash] = useHash();
React.useEffect(() => {
setHash('#list');
}, []);
return (
<>
<p>window.location.href: {window.location.href}</p>
<p>Edit hash: </p>
<input value={hash} onChange={e => setHash(e.target.value)} />
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,57 @@
---
title: React useHover hook
type: snippet
tags: [hooks,state,callback]
author: chalarangelo
cover: pink-flower-tree
dateModified: 2021-10-05T05:00:00-04:00
---
Handles the event of hovering over the wrapped component.
- Use the `useState()` hook to create a variable that holds the hovering state.
- Use the `useCallback()` hook to memoize two handler functions that update the state.
- Use the `useCallback()` hook to create a callback ref and create or update the listeners for the `'mouseover'` and `'mouseout'` events.
- Use the `useRef()` hook to keep track of the last node passed to `callbackRef` to be able to remove its listeners.
```jsx
const useHover = () => {
const [isHovering, setIsHovering] = React.useState(false);
const handleMouseOver = React.useCallback(() => setIsHovering(true), []);
const handleMouseOut = React.useCallback(() => setIsHovering(false), []);
const nodeRef = React.useRef();
const callbackRef = React.useCallback(
node => {
if (nodeRef.current) {
nodeRef.current.removeEventListener('mouseover', handleMouseOver);
nodeRef.current.removeEventListener('mouseout', handleMouseOut);
}
nodeRef.current = node;
if (nodeRef.current) {
nodeRef.current.addEventListener('mouseover', handleMouseOver);
nodeRef.current.addEventListener('mouseout', handleMouseOut);
}
},
[handleMouseOver, handleMouseOut]
);
return [callbackRef, isHovering];
};
```
```jsx
const MyApp = () => {
const [hoverRef, isHovering] = useHover();
return <div ref={hoverRef}>{isHovering ? 'Hovering' : 'Not hovering'}</div>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,57 @@
---
title: React useIntersectionObserver hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: budapest-palace
dateModified: 2021-09-10T05:00:00-04:00
---
Observes visibility changes for a given element.
- Use the `useState()` hook to store the intersection value of the given element.
- Create an `IntersectionObserver` with the given `options` and an appropriate callback to update the `isIntersecting` state variable.
- Use the `useEffect()` hook to call `IntersectionObserver.observe()` when mounting the component and clean up using `IntersectionObserver.unobserve()` when unmounting.
```jsx
const useIntersectionObserver = (ref, options) => {
const [isIntersecting, setIsIntersecting] = React.useState(false);
React.useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsIntersecting(entry.isIntersecting);
}, options);
if (ref.current) {
observer.observe(ref.current);
}
return () => {
observer.unobserve(ref.current);
};
}, []);
return isIntersecting;
};
```
```jsx
const MyApp = () => {
const ref = React.useRef();
const onScreen = useIntersectionObserver(ref, { threshold: 0.5 });
return (
<div>
<div style={{ height: '100vh' }}>Scroll down</div>
<div style={{ height: '100vh' }} ref={ref}>
<p>{onScreen ? 'Element is on screen.' : 'Scroll more!'}</p>
</div>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,50 @@
---
title: React useInterval hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: digital-nomad-13
dateModified: 2020-11-16T14:17:53+02:00
---
Implements `setInterval()` in a declarative manner.
- Create a custom hook that takes a `callback` and a `delay`.
- Use the `useRef()` hook to create a `ref` for the callback function.
- Use a `useEffect()` hook to remember the latest `callback` whenever it changes.
- Use a `useEffect()` hook dependent on `delay` to set up the interval and clean up.
```jsx
const useInterval = (callback, delay) => {
const savedCallback = React.useRef();
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
const tick = () => {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
```
```jsx
const Timer = props => {
const [seconds, setSeconds] = React.useState(0);
useInterval(() => {
setSeconds(seconds + 1);
}, 1000);
return <p>{seconds}</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Timer />
);
```

View File

@ -0,0 +1,31 @@
---
title: React useIsomporphicEffect hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: jars-on-shelf-2
dateModified: 2021-10-13T19:29:39+02:00
---
Resolves to `useEffect()` on the server and `useLayoutEffect()` on the client.
- Use `typeof` to check if the `Window` object is defined. If it is, return the `useLayoutEffect()`. Otherwise return `useEffect()`.
```jsx
const useIsomorphicEffect =
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
```
```jsx
const MyApp = () => {
useIsomorphicEffect(() => {
window.console.log('Hello');
}, []);
return null;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,53 @@
---
title: React useKeyPress hook
type: snippet
tags: [hooks,state,effect,event]
author: chalarangelo
cover: yellow-white-mug-1
dateModified: 2021-09-07T05:00:00-04:00
---
Listens for changes in the pressed state of a given key.
- Use the `useState()` hook to create a state variable that holds the pressed state of the given key.
- Define two handler functions that update the state variable on key down or key up accordingly.
- Use the `useEffect()` hook and `EventTarget.addEventListener()` to handle the `'keydown'` and `'keyup'` events.
- Use `EventTarget.removeEventListener()` to perform cleanup after the component is unmounted.
```jsx
const useKeyPress = targetKey => {
const [keyPressed, setKeyPressed] = React.useState(false);
const downHandler = ({ key }) => {
if (key === targetKey) setKeyPressed(true);
};
const upHandler = ({ key }) => {
if (key === targetKey) setKeyPressed(false);
};
React.useEffect(() => {
window.addEventListener('keydown', downHandler);
window.addEventListener('keyup', upHandler);
return () => {
window.removeEventListener('keydown', downHandler);
window.removeEventListener('keyup', upHandler);
};
}, []);
return keyPressed;
};
```
```jsx
const MyApp = () => {
const wPressed = useKeyPress('w');
return <p>The "w" key is {!wPressed ? 'not ' : ''}pressed!</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,54 @@
---
title: React useLocalStorage hook
type: snippet
tags: [hooks,state]
author: chalarangelo
cover: houses-rock-sea
dateModified: 2021-09-13T05:00:00-04:00
---
Creates a stateful value that is persisted to `localStorage`, and a function to update it.
- Use the `useState()` hook with a function to initialize its value lazily.
- Use a `try...catch` block and `Storage.getItem()` to try and get the value from `Window.localStorage`. If no value is found, use `Storage.setItem()` to store the `defaultValue` and use it as the initial state. If an error occurs, use `defaultValue` as the initial state.
- Define a function that will update the state variable with the passed value and use `Storage.setItem()` to store it.
```jsx
const useLocalStorage = (keyName, defaultValue) => {
const [storedValue, setStoredValue] = React.useState(() => {
try {
const value = window.localStorage.getItem(keyName);
if (value) {
return JSON.parse(value);
} else {
window.localStorage.setItem(keyName, JSON.stringify(defaultValue));
return defaultValue;
}
} catch (err) {
return defaultValue;
}
});
const setValue = newValue => {
try {
window.localStorage.setItem(keyName, JSON.stringify(newValue));
} catch (err) {}
setStoredValue(newValue);
};
return [storedValue, setValue];
};
```
```jsx
const MyApp = () => {
const [name, setName] = useLocalStorage('name', 'John');
return <input value={name} onChange={e => setName(e.target.value)} />;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

71
react/snippets/use-map.md Normal file
View File

@ -0,0 +1,71 @@
---
title: React useMap hook
type: snippet
tags: [hooks,state]
author: chalarangelo
cover: work-hard-computer
dateModified: 2021-11-06T05:00:00-04:00
---
Creates a stateful `Map` object, and a set of functions to manipulate it.
- Use the `useState()` hook and the `Map` constructor to create a new `Map` from the `initialValue`.
- Use the `useMemo()` hook to create a set of non-mutating actions that manipulate the `map` state variable, using the state setter to create a new `Map` every time.
- Return the `map` state variable and the created `actions`.
```jsx
const useMap = initialValue => {
const [map, setMap] = React.useState(new Map(initialValue));
const actions = React.useMemo(
() => ({
set: (key, value) =>
setMap(prevMap => {
const nextMap = new Map(prevMap);
nextMap.set(key, value);
return nextMap;
}),
remove: (key, value) =>
setMap(prevMap => {
const nextMap = new Map(prevMap);
nextMap.delete(key, value);
return nextMap;
}),
clear: () => setMap(new Map()),
}),
[setMap]
);
return [map, actions];
};
```
```jsx
const MyApp = () => {
const [map, { set, remove, clear }] = useMap([['apples', 10]]);
return (
<div>
<button onClick={() => set(Date.now(), new Date().toJSON())}>Add</button>
<button onClick={() => clear()}>Reset</button>
<button onClick={() => remove('apples')} disabled={!map.has('apples')}>
Remove apples
</button>
<pre>
{JSON.stringify(
[...map.entries()].reduce(
(acc, [key, value]) => ({ ...acc, [key]: value }),
{}
),
null,
2
)}
</pre>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,49 @@
---
title: React useMediaQuery hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: clay-pot-horizon
dateModified: 2021-10-13T19:29:39+02:00
---
Checks if the current environment matches a given media query and returns the appropriate value.
- Check if `Window` and `Window.matchMedia()` exist. Return `whenFalse` if not (e.g. SSR environment or unsupported browser).
- Use `Window.matchMedia()` to match the given `query`. Cast its `matches` property to a boolean and store in a state variable, `match`, using the `useState()` hook.
- Use the `useEffect()` hook to add a listener for changes and to clean up the listeners after the hook is destroyed.
- Return either `whenTrue` or `whenFalse` based on the value of `match`.
```jsx
const useMediaQuery = (query, whenTrue, whenFalse) => {
if (typeof window === 'undefined' || typeof window.matchMedia === 'undefined')
return whenFalse;
const mediaQuery = window.matchMedia(query);
const [match, setMatch] = React.useState(!!mediaQuery.matches);
React.useEffect(() => {
const handler = () => setMatch(!!mediaQuery.matches);
mediaQuery.addListener(handler);
return () => mediaQuery.removeListener(handler);
}, []);
return match ? whenTrue : whenFalse;
};
```
```jsx
const ResponsiveText = () => {
const text = useMediaQuery(
'(max-width: 400px)',
'Less than 400px wide',
'More than 400px wide'
);
return <span>{text}</span>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<ResponsiveText />
);
```

View File

@ -0,0 +1,53 @@
---
title: React useMergeState hook
type: snippet
tags: [hooks,state]
author: chalarangelo
cover: digital-nomad-6
dateModified: 2021-09-23T05:00:00-04:00
---
Creates a stateful value, and a function to update it by merging the new state provided.
- Use the `useState()` hook to create a state variable, initializing it to `initialState`.
- Create a function that will update the state variable by merging the new state provided with the existing one. If the new state is a function, call it with the previous state as the argument and use the result.
- Omit the argument, to initialize the state variable with an empty object (`{}`).
```jsx
const useMergeState = (initialState = {}) => {
const [value, setValue] = React.useState(initialState);
const mergeState = newState => {
if (typeof newState === 'function') newState = newState(value);
setValue({ ...value, ...newState });
};
return [value, mergeState];
};
```
```jsx
const MyApp = () => {
const [data, setData] = useMergeState({ name: 'John', age: 20 });
return (
<>
<input
value={data.name}
onChange={e => setData({ name: e.target.value })}
/>
<button onClick={() => setData(({ age }) => ({ age: age - 1 }))}>
-
</button>
{data.age}
<button onClick={() => setData(({ age }) => ({ age: age + 1 }))}>
+
</button>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,83 @@
---
title: React useMutationObserver hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: laptop-plants
dateModified: 2021-11-23T05:00:00-04:00
---
Watches for changes made to the DOM tree, using a `MutationObserver`
- Use a `useEffect()` hook that depends on the values of `callback` and `options`.
- Check if the given `ref` is initialized. If it is, create a new `MutationObserver` and pass it the `callback`.
- Call `MutationObserver.observe()` with the given `options` to watch the given `ref` for changes.
- Use `MutationObserver.disconnect()` to remove the observer from the `ref` when the component unmounts.
```jsx
const useMutationObserver = (
ref,
callback,
options = {
attributes: true,
characterData: true,
childList: true,
subtree: true,
}
) => {
React.useEffect(() => {
if (ref.current) {
const observer = new MutationObserver(callback);
observer.observe(ref.current, options);
return () => observer.disconnect();
}
}, [callback, options]);
};
```
```jsx
const App = () => {
const mutationRef = React.useRef();
const [mutationCount, setMutationCount] = React.useState(0);
const incrementMutationCount = () => {
return setMutationCount(mutationCount + 1);
};
useMutationObserver(mutationRef, incrementMutationCount);
const [content, setContent] = React.useState('Hello world');
return (
<>
<label for="content-input">Edit this to update the text:</label>
<textarea
id="content-input"
style={{ width: '100%' }}
value={content}
onChange={e => setContent(e.target.value)}
/>
<div
style={{ width: '100%' }}
ref={mutationRef}
>
<div
style={{
resize: 'both',
overflow: 'auto',
maxWidth: '100%',
border: '1px solid black',
}}
>
<h2>Resize or change the content:</h2>
<p>{content}</p>
</div>
</div>
<div>
<h3>Mutation count {mutationCount}</h3>
</div>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,53 @@
---
title: React useNavigatorOnLine hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: digital-nomad-7
dateModified: 2020-11-16T14:17:53+02:00
---
Checks if the client is online or offline.
- Create a function, `getOnLineStatus`, that uses the `Navigator.onLine` web API to get the online status of the client.
- Use the `useState()` hook to create an appropriate state variable, `status`, and setter.
- Use the `useEffect()` hook to add listeners for appropriate events, updating state, and cleanup those listeners when unmounting.
- Finally return the `status` state variable.
```jsx
const getOnLineStatus = () =>
typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean'
? navigator.onLine
: true;
const useNavigatorOnLine = () => {
const [status, setStatus] = React.useState(getOnLineStatus());
const setOnline = () => setStatus(true);
const setOffline = () => setStatus(false);
React.useEffect(() => {
window.addEventListener('online', setOnline);
window.addEventListener('offline', setOffline);
return () => {
window.removeEventListener('online', setOnline);
window.removeEventListener('offline', setOffline);
};
}, []);
return status;
};
```
```jsx
const StatusIndicator = () => {
const isOnline = useNavigatorOnLine();
return <span>You are {isOnline ? 'online' : 'offline'}.</span>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<StatusIndicator />
);
```

View File

@ -0,0 +1,55 @@
---
title: React useOnGlobalEvent hook
type: snippet
tags: [hooks,effect,event]
author: chalarangelo
cover: tropical-bike
dateModified: 2021-12-22T05:00:00-04:00
---
Executes a callback whenever an event occurs on the global object.
- Use the `useRef()` hook to create a variable, `listener`, which will hold the listener reference.
- Use the `useRef()` hook to create a variable that will hold the previous values of the `type` and `options` arguments.
- Use the `useEffect()` hook and `EventTarget.addEventListener()` to listen to the given event `type` on the `Window` global object.
- Use `EventTarget.removeEventListener()` to remove any existing listeners and clean up when the component unmounts.
```jsx
const useOnGlobalEvent = (type, callback, options) => {
const listener = React.useRef(null);
const previousProps = React.useRef({ type, options });
React.useEffect(() => {
const { type: previousType, options: previousOptions } = previousProps;
if (listener.current) {
window.removeEventListener(
previousType,
listener.current,
previousOptions
);
}
listener.current = window.addEventListener(type, callback, options);
previousProps.current = { type, options };
return () => {
window.removeEventListener(type, listener.current, options);
};
}, [callback, type, options]);
};
```
```jsx
const App = () => {
useOnGlobalEvent('mousemove', e => {
console.log(`(${e.x}, ${e.y})`);
});
return <p>Move your mouse around</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,42 @@
---
title: React useOnWindowResize hook
type: snippet
tags: [hooks,effect,event]
author: chalarangelo
cover: flower-camera
dateModified: 2021-12-01T05:00:00-04:00
---
Executes a callback whenever the window is resized.
- Use the `useRef()` hook to create a variable, `listener`, which will hold the listener reference.
- Use the `useEffect()` hook and `EventTarget.addEventListener()` to listen to the `'resize'` event of the `Window` global object.
- Use `EventTarget.removeEventListener()` to remove any existing listeners and clean up when the component unmounts.
```jsx
const useOnWindowResize = callback => {
const listener = React.useRef(null);
React.useEffect(() => {
if (listener.current)
window.removeEventListener('resize', listener.current);
listener.current = window.addEventListener('resize', callback);
return () => {
window.removeEventListener('resize', listener.current);
};
}, [callback]);
};
```
```jsx
const App = () => {
useOnWindowResize(() =>
console.log(`window size: (${window.innerWidth}, ${window.innerHeight})`)
);
return <p>Resize the window and check the console</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,40 @@
---
title: React useOnWindowScroll hook
type: snippet
tags: [hooks,effect,event]
author: chalarangelo
cover: planning
dateModified: 2021-12-08T05:00:00-04:00
---
Executes a callback whenever the window is scrolled.
- Use the `useRef()` hook to create a variable, `listener`, which will hold the listener reference.
- Use the `useEffect()` hook and `EventTarget.addEventListener()` to listen to the `'scroll'` event of the `Window` global object.
- Use `EventTarget.removeEventListener()` to remove any existing listeners and clean up when the component unmounts.
```jsx
const useOnWindowScroll = callback => {
const listener = React.useRef(null);
React.useEffect(() => {
if (listener.current)
window.removeEventListener('scroll', listener.current);
listener.current = window.addEventListener('scroll', callback);
return () => {
window.removeEventListener('scroll', listener.current);
};
}, [callback]);
};
```
```jsx
const App = () => {
useOnWindowScroll(() => console.log(`scroll Y: ${window.pageYOffset}`));
return <p style={{ height: '300vh' }}>Scroll and check the console</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,87 @@
---
title: React usePersistedState hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: red-berries
dateModified: 2021-10-13T19:29:39+02:00
---
Returns a stateful value, persisted in `localStorage`, and a function to update it.
- Use the `useState()` hook to initialize the `value` to `defaultValue`.
- Use the `useRef()` hook to create a ref that will hold the `name` of the value in `Window.localStorage`.
- Use 3 instances of the `useEffect()` hook for initialization, `value` change and `name` change respectively.
- When the component is first mounted, use `Storage.getItem()` to update `value` if there's a stored value or `Storage.setItem()` to persist the current value.
- When `value` is updated, use `Storage.setItem()` to store the new value.
- When `name` is updated, use `Storage.setItem()` to create the new key, update the `nameRef` and use `Storage.removeItem()` to remove the previous key from `Window.localStorage`.
- **Note:** The hook is meant for use with primitive values (i.e. not objects) and doesn't account for changes to `Window.localStorage` due to other code. Both of these issues can be easily handled (e.g. JSON serialization and handling the `'storage'` event).
```jsx
const usePersistedState = (name, defaultValue) => {
const [value, setValue] = React.useState(defaultValue);
const nameRef = React.useRef(name);
React.useEffect(() => {
try {
const storedValue = localStorage.getItem(name);
if (storedValue !== null) setValue(storedValue);
else localStorage.setItem(name, defaultValue);
} catch {
setValue(defaultValue);
}
}, []);
React.useEffect(() => {
try {
localStorage.setItem(nameRef.current, value);
} catch {}
}, [value]);
React.useEffect(() => {
const lastName = nameRef.current;
if (name !== lastName) {
try {
localStorage.setItem(name, value);
nameRef.current = name;
localStorage.removeItem(lastName);
} catch {}
}
}, [name]);
return [value, setValue];
};
```
```jsx
const MyComponent = ({ name }) => {
const [val, setVal] = usePersistedState(name, 10);
return (
<input
value={val}
onChange={e => {
setVal(e.target.value);
}}
/>
);
};
const MyApp = () => {
const [name, setName] = React.useState('my-value');
return (
<>
<MyComponent name={name} />
<input
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,56 @@
---
title: React usePortal hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: interior-3
dateModified: 2022-01-05T05:00:00-04:00
---
Creates a portal, allowing rendering of children outside the parent component.
- Use the `useState()` hook to create a state variable that holds the `render()` and `remove()` functions for the portal.
- Use `ReactDOM.createPortal()` and `ReactDOM.unmountComponentAtNode()` to create a portal and a function to remove it. Use the `useCallback()` hook to wrap and memoize these functions as `createPortal()`.
- Use the `useEffect()` hook to call `createPortal()` and update the state variable any time `el`'s value changes.
- Finally, return the `render()` function of the state variable.
```jsx
const usePortal = el => {
const [portal, setPortal] = React.useState({
render: () => null,
remove: () => null,
});
const createPortal = React.useCallback(el => {
const Portal = ({ children }) => ReactDOM.createPortal(children, el);
const remove = () => ReactDOM.unmountComponentAtNode(el);
return { render: Portal, remove };
}, []);
React.useEffect(() => {
if (el) portal.remove();
const newPortal = createPortal(el);
setPortal(newPortal);
return () => newPortal.remove(el);
}, [el]);
return portal.render;
};
```
```jsx
const App = () => {
const Portal = usePortal(document.querySelector('title'));
return (
<p>
Hello world!
<Portal>Portalized Title</Portal>
</p>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,44 @@
---
title: React usePrevious hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: couch-laptop
dateModified: 2020-11-16T14:17:53+02:00
---
Stores the previous state or props.
- Create a custom hook that takes a `value`.
- Use the `useRef()` hook to create a `ref` for the `value`.
- Use the `useEffect()` hook to remember the latest `value`.
```jsx
const usePrevious = value => {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
};
```
```jsx
const Counter = () => {
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.createRoot(document.getElementById('root')).render(
<Counter />
);
```

View File

@ -0,0 +1,48 @@
---
title: React useRequestAnimationFrame hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: aerial-view-port
dateModified: 2021-12-29T05:00:00-04:00
---
Runs an animating function, calling it before every repaint.
- Use the `useRef()` hook to create two variables. `requestRef` will hold the last request id and `previousTimeRef` will hold the last timestamp.
- Define a function, `animate`, which handles updating these variables, runs the `callback` and calls `Window.requestAnimationFrame()` perpetually.
- Use the `useEffect()` hook with an empty array to initialize the value of `requestRef` using `Window.requestAnimationFrame()`. Use the returned value and `Window.cancelAnimationFrame()` to clean up when the component unmounts.
```jsx
const useRequestAnimationFrame = callback => {
const requestRef = React.useRef();
const previousTimeRef = React.useRef();
const animate = time => {
if (previousTimeRef.current) callback(time - previousTimeRef.current);
previousTimeRef.current = time;
requestRef.current = requestAnimationFrame(animate);
};
React.useEffect(() => {
requestRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(requestRef.current);
}, []);
};
```
```jsx
const Counter = () => {
const [count, setCount] = React.useState(0);
useRequestAnimationFrame(deltaTime => {
setCount(prevCount => (prevCount + deltaTime * 0.01) % 100);
});
return <p>{Math.round(count)}</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Counter />
);
```

View File

@ -0,0 +1,94 @@
---
title: React useScript hook
type: snippet
tags: [hooks,effect,state,event]
author: chalarangelo
cover: travel-mug-3
dateModified: 2021-10-08T05:00:00-04:00
---
Dynamically loads an external script.
- Use the `useState()` hook to create a state variable for the load status of the script.
- Use the `useEffect()` hook to handle all the logic for loading and unloading the script anytime the `src` changes.
- If no `src` value is present, set the `status` to `'idle'` and return.
- Use `Document.querySelector()` to check if a `<script>` element with the appropriate `src` value exists.
- If no relevant element exists, use `Document.createElement()` to create one and give it the appropriate attributes.
- Use the `data-status` attribute as a way to indicate the status of the script, setting it to `'loading'` initially.
- If a relevant element exists, skip initialization and update the `status` from its `data-status` attribute. This ensures that no duplicate element will be created.
- Use `EventTarget.addEventListener()` to listen for `'load'` and `'error'` events and handle them by updating the `data-status` attribute and the `status` state variable.
- Finally, when the component unmounts, use `Document.removeEventListener()` to remove any listeners bound to the element.
```jsx
const useScript = src => {
const [status, setStatus] = React.useState(src ? 'loading' : 'idle');
React.useEffect(() => {
if (!src) {
setStatus('idle');
return;
}
let script = document.querySelector(`script[src="${src}"]`);
if (!script) {
script = document.createElement('script');
script.src = src;
script.async = true;
script.setAttribute('data-status', 'loading');
document.body.appendChild(script);
const setDataStatus = event => {
script.setAttribute(
'data-status',
event.type === 'load' ? 'ready' : 'error'
);
};
script.addEventListener('load', setDataStatus);
script.addEventListener('error', setDataStatus);
} else {
setStatus(script.getAttribute('data-status'));
}
const setStateStatus = event => {
setStatus(event.type === 'load' ? 'ready' : 'error');
};
script.addEventListener('load', setStateStatus);
script.addEventListener('error', setStateStatus);
return () => {
if (script) {
script.removeEventListener('load', setStateStatus);
script.removeEventListener('error', setStateStatus);
}
};
}, [src]);
return status;
};
```
```jsx
const script =
'data:text/plain;charset=utf-8;base64,KGZ1bmN0aW9uKCl7IGNvbnNvbGUubG9nKCdIZWxsbycpIH0pKCk7';
const Child = () => {
const status = useScript(script);
return <p>Child status: {status}</p>;
};
const MyApp = () => {
const status = useScript(script);
return (
<>
<p>Parent status: {status}</p>
<Child />
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,69 @@
---
title: React useSearchParam hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: coffee-phone-tray-3
dateModified: 2021-10-13T05:00:00-04:00
---
Tracks the browser's location search param.
- Use the `useCallback()` hook to create a callback that uses the `URLSearchParams` constructor to get the current value of `param`.
- Use the `useState()` hook to create a state variable that holds the current value of the `param`.
- Use the `useEffect()` hook to set appropriate event listeners to update the state variable when mounting and clean them up when unmounting.
```jsx
const useSearchParam = param => {
const getValue = React.useCallback(
() => new URLSearchParams(window.location.search).get(param),
[param]
);
const [value, setValue] = React.useState(getValue);
React.useEffect(() => {
const onChange = () => {
setValue(getValue());
};
window.addEventListener('popstate', onChange);
window.addEventListener('pushstate', onChange);
window.addEventListener('replacestate', onChange);
return () => {
window.removeEventListener('popstate', onChange);
window.removeEventListener('pushstate', onChange);
window.removeEventListener('replacestate', onChange);
};
}, []);
return value;
};
```
```jsx
const MyApp = () => {
const post = useSearchParam('post');
return (
<>
<p>Post param value: {post || 'null'}</p>
<button
onClick={() =>
history.pushState({}, '', location.pathname + '?post=42')
}
>
View post 42
</button>
<button onClick={() => history.pushState({}, '', location.pathname)}>
Exit
</button>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,54 @@
---
title: React useSessionStorage hook
type: snippet
tags: [hooks,state]
author: chalarangelo
cover: white-laptop
dateModified: 2021-09-15T05:00:00-04:00
---
Creates a stateful value that is persisted to `sessionStorage`, and a function to update it.
- Use the `useState()` hook with a function to initialize its value lazily.
- Use a `try...catch` block and `Storage.getItem()` to try and get the value from `Window.sessionStorage`. If no value is found, use `Storage.setItem()` to store the `defaultValue` and use it as the initial state. If an error occurs, use `defaultValue` as the initial state.
- Define a function that will update the state variable with the passed value and use `Storage.setItem()` to store it.
```jsx
const useSessionStorage = (keyName, defaultValue) => {
const [storedValue, setStoredValue] = React.useState(() => {
try {
const value = window.sessionStorage.getItem(keyName);
if (value) {
return JSON.parse(value);
} else {
window.sessionStorage.setItem(keyName, JSON.stringify(defaultValue));
return defaultValue;
}
} catch (err) {
return defaultValue;
}
});
const setValue = newValue => {
try {
window.sessionStorage.setItem(keyName, JSON.stringify(newValue));
} catch (err) {}
setStoredValue(newValue);
};
return [storedValue, setValue];
};
```
```jsx
const MyApp = () => {
const [name, setName] = useSessionStorage('name', 'John');
return <input value={name} onChange={e => setName(e.target.value)} />;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

53
react/snippets/use-set.md Normal file
View File

@ -0,0 +1,53 @@
---
title: React useSet hook
type: snippet
tags: [hooks,state]
author: chalarangelo
cover: engine
dateModified: 2021-11-01T05:00:00-04:00
---
Creates a stateful `Set` object, and a set of functions to manipulate it.
- Use the `useState()` hook and the `Set` constructor to create a new `Set` from the `initialValue`.
- Use the `useMemo()` hook to create a set of non-mutating actions that manipulate the `set` state variable, using the state setter to create a new `Set` every time.
- Return the `set` state variable and the created `actions`.
```jsx
const useSet = initialValue => {
const [set, setSet] = React.useState(new Set(initialValue));
const actions = React.useMemo(
() => ({
add: item => setSet(prevSet => new Set([...prevSet, item])),
remove: item =>
setSet(prevSet => new Set([...prevSet].filter(i => i !== item))),
clear: () => setSet(new Set()),
}),
[setSet]
);
return [set, actions];
};
```
```jsx
const MyApp = () => {
const [set, { add, remove, clear }] = useSet(new Set(['apples']));
return (
<div>
<button onClick={() => add(String(Date.now()))}>Add</button>
<button onClick={() => clear()}>Reset</button>
<button onClick={() => remove('apples')} disabled={!set.has('apples')}>
Remove apples
</button>
<pre>{JSON.stringify([...set], null, 2)}</pre>
</div>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

63
react/snippets/use-ssr.md Normal file
View File

@ -0,0 +1,63 @@
---
title: React useSSR hook
type: snippet
tags: [hooks,effect,state,memo]
author: chalarangelo
cover: interior-6
dateModified: 2021-03-10T06:38:42+02:00
---
Checks if the code is running on the browser or the server.
- Create a custom hook that returns an appropriate object.
- Use `typeof`, `Window`, `Window.document` and `Document.createElement()` to check if the code is running on the browser.
- Use the `useState()` hook to define the `inBrowser` state variable.
- Use the `useEffect()` hook to update the `inBrowser` state variable and clean up at the end.
- Use the `useMemo()` hook to memoize the return values of the custom hook.
```jsx
const isDOMavailable = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
const useSSR = () => {
const [inBrowser, setInBrowser] = React.useState(isDOMavailable);
React.useEffect(() => {
setInBrowser(isDOMavailable);
return () => {
setInBrowser(false);
};
}, []);
const useSSRObject = React.useMemo(
() => ({
isBrowser: inBrowser,
isServer: !inBrowser,
canUseWorkers: typeof Worker !== 'undefined',
canUseEventListeners: inBrowser && !!window.addEventListener,
canUseViewport: inBrowser && !!window.screen
}),
[inBrowser]
);
return React.useMemo(
() => Object.assign(Object.values(useSSRObject), useSSRObject),
[inBrowser]
);
};
```
```jsx
const SSRChecker = props => {
let { isBrowser, isServer } = useSSR();
return <p>{isBrowser ? 'Running on browser' : 'Running on server'}</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<SSRChecker />
);
```

View File

@ -0,0 +1,50 @@
---
title: React useTimeout hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: interior-10
dateModified: 2020-11-16T14:17:53+02:00
---
Implements `setTimeout()` in a declarative manner.
- Create a custom hook that takes a `callback` and a `delay`.
- Use the `useRef()` hook to create a `ref` for the callback function.
- Use the `useEffect()` hook to remember the latest callback.
- Use the `useEffect()` hook to set up the timeout and clean up.
```jsx
const useTimeout = (callback, delay) => {
const savedCallback = React.useRef();
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
const tick = () => {
savedCallback.current();
}
if (delay !== null) {
let id = setTimeout(tick, delay);
return () => clearTimeout(id);
}
}, [delay]);
};
```
```jsx
const OneSecondTimer = props => {
const [seconds, setSeconds] = React.useState(0);
useTimeout(() => {
setSeconds(seconds + 1);
}, 1000);
return <p>{seconds}</p>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<OneSecondTimer />
);
```

View File

@ -0,0 +1,53 @@
---
title: React useTitle hook
type: snippet
tags: [hooks,effect]
author: chalarangelo
cover: blue-lake
dateModified: 2021-09-27T05:00:00-04:00
---
Sets the title of the page
- Use `typeof` to determine if the `Document` is defined or not.
- Use the `useRef()` hook to store the original title of the `Document`, if defined.
- Use the `useEffect()` hook to set `Document.title` to the passed value when the component mounts and clean up when unmounting.
```jsx
const useTitle = title => {
const documentDefined = typeof document !== 'undefined';
const originalTitle = React.useRef(documentDefined ? document.title : null);
React.useEffect(() => {
if (!documentDefined) return;
if (document.title !== title) document.title = title;
return () => {
document.title = originalTitle.current;
};
}, []);
};
```
```jsx
const Alert = () => {
useTitle('Alert');
return <p>Alert! Title has changed</p>;
};
const MyApp = () => {
const [alertOpen, setAlertOpen] = React.useState(false);
return (
<>
<button onClick={() => setAlertOpen(!alertOpen)}>Toggle alert</button>
{alertOpen && <Alert />}
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,34 @@
---
title: React useToggler hook
type: snippet
tags: [hooks,state,callback]
author: chalarangelo
cover: tram-car-2
dateModified: 2020-11-27T09:41:31+02:00
---
Provides a boolean state variable that can be toggled between its two states.
- Use the `useState()` hook to create the `value` state variable and its setter.
- Create a function that toggles the value of the `value` state variable and memoize it, using the `useCallback()` hook.
- Return the `value` state variable and the memoized toggler function.
```jsx
const useToggler = initialState => {
const [value, setValue] = React.useState(initialState);
const toggleValue = React.useCallback(() => setValue(prev => !prev), []);
return [value, toggleValue];
};
```
```jsx
const Switch = () => {
const [val, toggleVal] = useToggler(false);
return <button onClick={toggleVal}>{val ? 'ON' : 'OFF'}</button>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<Switch />
);
```

View File

@ -0,0 +1,41 @@
---
title: React useUnload hook
type: snippet
tags: [hooks,effect,event]
cover: digital-nomad-14
dateModified: 2020-11-29T14:16:36+02:00
---
Handles the `beforeunload` window event.
- Use the `useRef()` hook to create a ref for the callback function, `fn`.
- Use the `useEffect()` hook and `EventTarget.addEventListener()` to handle the `'beforeunload'` (when the user is about to close the window).
- Use `EventTarget.removeEventListener()` to perform cleanup after the component is unmounted.
```jsx
const useUnload = fn => {
const cb = React.useRef(fn);
React.useEffect(() => {
const onUnload = cb.current;
window.addEventListener('beforeunload', onUnload);
return () => {
window.removeEventListener('beforeunload', onUnload);
};
}, [cb]);
};
```
```jsx
const App = () => {
useUnload(e => {
e.preventDefault();
const exit = confirm('Are you sure you want to leave?');
if (exit) window.close();
});
return <div>Try closing the window.</div>;
};
ReactDOM.createRoot(document.getElementById('root')).render(
<App />
);
```

View File

@ -0,0 +1,36 @@
---
title: React useUpdate hook
type: snippet
tags: [components,reducer]
author: chalarangelo
cover: lavender-shelf
dateModified: 2021-09-24T05:00:00-04:00
---
Forces the component to re-render when called.
- Use the `useReducer()` hook that creates a new object every time it's updated and return its dispatch.
```jsx
const useUpdate = () => {
const [, update] = React.useReducer(() => ({}));
return update;
};
```
```jsx
const MyApp = () => {
const update = useUpdate();
return (
<>
<div>Time: {Date.now()}</div>
<button onClick={update}>Update</button>
</>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```

View File

@ -0,0 +1,54 @@
---
title: React useWindowSize hook
type: snippet
tags: [hooks,state,effect]
author: chalarangelo
cover: polar-bear
dateModified: 2021-10-18T05:00:00-04:00
---
Tracks the dimensions of the browser window.
- Use the `useState()` hook to initialize a state variable that will hold the window's dimensions. Initialize with both values set to `undefined` to avoid mismatch between server and client renders.
- Create a function that uses `Window.innerWidth` and `Window.innerHeight` to update the state variable.
- Use the `useEffect()` hook to set an appropriate listener for the `'resize'` event on mount and clean it up when unmounting.
```jsx
const useWindowSize = () => {
const [windowSize, setWindowSize] = React.useState({
width: undefined,
height: undefined,
});
React.useEffect(() => {
const handleResize = () =>
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener('resize', handleResize);
handleResize();
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowSize;
};
```
```jsx
const MyApp = () => {
const { width, height } = useWindowSize();
return (
<p>
Window size: ({width} x {height})
</p>
);
};
ReactDOM.createRoot(document.getElementById('root')).render(
<MyApp />
);
```