Merge 30-seconds-of-react
This commit is contained in:
29
react/snippet-template.md
Normal file
29
react/snippet-template.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
86
react/snippets/accordion.md
Normal file
86
react/snippets/accordion.md
Normal 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
107
react/snippets/alert.md
Normal 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" />
|
||||
);
|
||||
```
|
||||
41
react/snippets/auto-link.md
Normal file
41
react/snippets/auto-link.md
Normal 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
26
react/snippets/callto.md
Normal 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>
|
||||
);
|
||||
```
|
||||
67
react/snippets/carousel.md
Normal file
67
react/snippets/carousel.md
Normal 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>
|
||||
]}
|
||||
/>
|
||||
);
|
||||
```
|
||||
61
react/snippets/collapse.md
Normal file
61
react/snippets/collapse.md
Normal 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>
|
||||
);
|
||||
```
|
||||
44
react/snippets/controlled-input.md
Normal file
44
react/snippets/controlled-input.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
66
react/snippets/count-down.md
Normal file
66
react/snippets/count-down.md
Normal 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} />
|
||||
);
|
||||
```
|
||||
29
react/snippets/data-list.md
Normal file
29
react/snippets/data-list.md
Normal 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 />
|
||||
</>
|
||||
);
|
||||
```
|
||||
42
react/snippets/data-table.md
Normal file
42
react/snippets/data-table.md
Normal 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
107
react/snippets/file-drop.md
Normal 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} />
|
||||
);
|
||||
```
|
||||
79
react/snippets/lazy-load-image.md
Normal file
79
react/snippets/lazy-load-image.md
Normal 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"
|
||||
/>
|
||||
);
|
||||
```
|
||||
46
react/snippets/limited-textarea.md
Normal file
46
react/snippets/limited-textarea.md
Normal 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!" />
|
||||
);
|
||||
```
|
||||
63
react/snippets/limited-word-textarea.md
Normal file
63
react/snippets/limited-word-textarea.md
Normal 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
70
react/snippets/loader.md
Normal 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
32
react/snippets/mailto.md
Normal 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>
|
||||
);
|
||||
```
|
||||
56
react/snippets/mapped-table.md
Normal file
56
react/snippets/mapped-table.md
Normal 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
157
react/snippets/modal.md
Normal 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}>
|
||||
×
|
||||
</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 />
|
||||
);
|
||||
```
|
||||
58
react/snippets/multiselect-checkbox.md
Normal file
58
react/snippets/multiselect-checkbox.md
Normal 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);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
```
|
||||
31
react/snippets/password-revealer.md
Normal file
31
react/snippets/password-revealer.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
110
react/snippets/ripple-button.md
Normal file
110
react/snippets/ripple-button.md
Normal 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
48
react/snippets/select.md
Normal 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
40
react/snippets/slider.md
Normal 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)} />
|
||||
);
|
||||
```
|
||||
65
react/snippets/star-rating.md
Normal file
65
react/snippets/star-rating.md
Normal 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
95
react/snippets/tabs.md
Normal 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
122
react/snippets/tag-input.md
Normal 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']} />
|
||||
);
|
||||
```
|
||||
41
react/snippets/text-area.md
Normal file
41
react/snippets/text-area.md
Normal 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
50
react/snippets/toggle.md
Normal 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
72
react/snippets/tooltip.md
Normal 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
141
react/snippets/tree-view.md
Normal 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> {name}: </strong> : <span> </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" />
|
||||
);
|
||||
```
|
||||
34
react/snippets/uncontrolled-input.md
Normal file
34
react/snippets/uncontrolled-input.md
Normal 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}
|
||||
/>
|
||||
);
|
||||
```
|
||||
79
react/snippets/use-async.md
Normal file
79
react/snippets/use-async.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
66
react/snippets/use-body-scroll-lock.md
Normal file
66
react/snippets/use-body-scroll-lock.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
57
react/snippets/use-click-inside.md
Normal file
57
react/snippets/use-click-inside.md
Normal 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')} />
|
||||
);
|
||||
```
|
||||
57
react/snippets/use-click-outside.md
Normal file
57
react/snippets/use-click-outside.md
Normal 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')} />
|
||||
);
|
||||
```
|
||||
33
react/snippets/use-component-did-mount.md
Normal file
33
react/snippets/use-component-did-mount.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
53
react/snippets/use-component-did-update.md
Normal file
53
react/snippets/use-component-did-update.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
36
react/snippets/use-component-will-unmount.md
Normal file
36
react/snippets/use-component-will-unmount.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
66
react/snippets/use-copy-to-clipboard.md
Normal file
66
react/snippets/use-copy-to-clipboard.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
55
react/snippets/use-debounce.md
Normal file
55
react/snippets/use-debounce.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
40
react/snippets/use-default.md
Normal file
40
react/snippets/use-default.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
69
react/snippets/use-delayed-state.md
Normal file
69
react/snippets/use-delayed-state.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
40
react/snippets/use-effect-once.md
Normal file
40
react/snippets/use-effect-once.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
46
react/snippets/use-error.md
Normal file
46
react/snippets/use-error.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
58
react/snippets/use-event-listener.md
Normal file
58
react/snippets/use-event-listener.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
64
react/snippets/use-fetch.md
Normal file
64
react/snippets/use-fetch.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
52
react/snippets/use-form.md
Normal file
52
react/snippets/use-form.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
49
react/snippets/use-get-set.md
Normal file
49
react/snippets/use-get-set.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
63
react/snippets/use-hash.md
Normal file
63
react/snippets/use-hash.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
57
react/snippets/use-hover.md
Normal file
57
react/snippets/use-hover.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
57
react/snippets/use-intersection-observer.md
Normal file
57
react/snippets/use-intersection-observer.md
Normal 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 />
|
||||
);
|
||||
|
||||
```
|
||||
50
react/snippets/use-interval.md
Normal file
50
react/snippets/use-interval.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
31
react/snippets/use-isomporphic-effect.md
Normal file
31
react/snippets/use-isomporphic-effect.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
53
react/snippets/use-key-press.md
Normal file
53
react/snippets/use-key-press.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
54
react/snippets/use-local-storage.md
Normal file
54
react/snippets/use-local-storage.md
Normal 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
71
react/snippets/use-map.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
49
react/snippets/use-media-query.md
Normal file
49
react/snippets/use-media-query.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
53
react/snippets/use-merge-state.md
Normal file
53
react/snippets/use-merge-state.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
83
react/snippets/use-mutation-observer.md
Normal file
83
react/snippets/use-mutation-observer.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
53
react/snippets/use-navigator-on-line.md
Normal file
53
react/snippets/use-navigator-on-line.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
55
react/snippets/use-on-global-event.md
Normal file
55
react/snippets/use-on-global-event.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
42
react/snippets/use-on-window-resize.md
Normal file
42
react/snippets/use-on-window-resize.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
40
react/snippets/use-on-window-scroll.md
Normal file
40
react/snippets/use-on-window-scroll.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
87
react/snippets/use-persisted-state.md
Normal file
87
react/snippets/use-persisted-state.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
56
react/snippets/use-portal.md
Normal file
56
react/snippets/use-portal.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
44
react/snippets/use-previous.md
Normal file
44
react/snippets/use-previous.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
48
react/snippets/use-request-animation-frame.md
Normal file
48
react/snippets/use-request-animation-frame.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
94
react/snippets/use-script.md
Normal file
94
react/snippets/use-script.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
69
react/snippets/use-search-param.md
Normal file
69
react/snippets/use-search-param.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
54
react/snippets/use-session-storage.md
Normal file
54
react/snippets/use-session-storage.md
Normal 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
53
react/snippets/use-set.md
Normal 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
63
react/snippets/use-ssr.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
50
react/snippets/use-timeout.md
Normal file
50
react/snippets/use-timeout.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
53
react/snippets/use-title.md
Normal file
53
react/snippets/use-title.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
34
react/snippets/use-toggler.md
Normal file
34
react/snippets/use-toggler.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
41
react/snippets/use-unload.md
Normal file
41
react/snippets/use-unload.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
36
react/snippets/use-update.md
Normal file
36
react/snippets/use-update.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
54
react/snippets/use-window-size.md
Normal file
54
react/snippets/use-window-size.md
Normal 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 />
|
||||
);
|
||||
```
|
||||
Reference in New Issue
Block a user