Accordion
- title: Accordion
- tags: components,children,state,advanced
Renders an accordion menu with multiple collapsible content elements.
- Define an
AccordionItem
component, that renders a<button>
which is used to update the component and notify its parent via thehandleClick
callback. - Use the
isCollapsed
prop inAccordionItem
to determine its appearance and set an appropriateclassName
. - Define an
Accordion
component that uses theuseState()
hook to initialize the value of thebindIndex
state variable todefaultIndex
. - Filter
children
to remove unnecessary nodes except forAccordionItem
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 anAccordionItem
's<button>
. changeItem
executes the passed callback,onItemClick
, and updatesbindIndex
based on the clicked element.
.accordion-item.collapsed {
display: none;
}
.accordion-item.expanded {
display: block;
}
.accordion-button {
display: block;
width: 100%;
}
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}
/>
))}
</>
);
};
ReactDOM.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>,
document.getElementById('root')
);
Alert
- title: Alert
- tags: components,state,effect,beginner
Renders an alert component with type
prop.
- Use the
useState()
hook to create theisShown
andisLeaving
state variables and set both tofalse
initially. - Define
timeoutId
to keep the timer instance for clearing on component unmount. - Use the
useEffect()
hook to update the value ofisShown
totrue
and clear the interval by usingtimeoutId
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 setisShown
tofalse
viasetTimeout()
.
@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';
}
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>
)
);
};
ReactDOM.render(
<Alert type="info" message="This is info" />,
document.getElementById('root')
);
AutoLink
- title: AutoLink
- tags: components,fragment,regexp,intermediate
Renders a string as plaintext, with URLs converted to appropriate link elements.
- Use
String.prototype.split()
andString.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.
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;
})}
</>
);
};
ReactDOM.render(
<AutoLink text="foo bar baz http://example.org bar" />,
document.getElementById('root')
);
Callto
- title: Callto
- tags: components,beginner unlisted: true
Renders a link formatted to call a phone number (tel:
link).
- Use
phone
to create a<a>
element with an appropriatehref
attribute. - Render the link with
children
as its content.
const Callto = ({ phone, children }) => {
return <a href={`tel:${phone}`}>{children}</a>;
};
ReactDOM.render(
<Callto phone="+302101234567">Call me!</Callto>,
document.getElementById('root')
);
Carousel
- title: Carousel
- tags: components,children,state,effect,advanced
Renders a carousel component.
- Use the
useState()
hook to create theactive
state variable and give it a value of0
(index of the first item). - Use the
useEffect()
hook to update the value ofactive
to the index of the next item, usingsetTimeout
. - 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 computedclassName
.
.carousel {
position: relative;
}
.carousel-item {
position: absolute;
visibility: hidden;
}
.carousel-item.visible {
visibility: visible;
}
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>
);
};
ReactDOM.render(
<Carousel
carouselItems={[
<div>carousel item 1</div>,
<div>carousel item 2</div>,
<div>carousel item 3</div>
]}
/>,
document.getElementById('root')
);
Collapse
- title: Collapse
- tags: components,children,state,beginner
Renders a component with collapsible content.
- Use the
useState()
hook to create theisCollapsed
state variable with an initial value ofcollapsed
. - Use the
<button>
to change the component'sisCollapsed
state and the content of the component, passed down viachildren
. - Determine the appearance of the content, based on
isCollapsed
and apply the appropriateclassName
. - Update the value of the
aria-expanded
attribute based onisCollapsed
to make the component accessible.
.collapse-button {
display: block;
width: 100%;
}
.collapse-content.collapsed {
display: none;
}
.collapsed-content.expanded {
display: block;
}
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>
</>
);
};
ReactDOM.render(
<Collapse>
<h1>This is a collapse</h1>
<p>Hello world!</p>
</Collapse>,
document.getElementById('root')
);
ControlledInput
- title: ControlledInput
- tags: components,input,intermediate
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 theonValueChange
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.
const ControlledInput = ({ value, onValueChange, ...rest }) => {
return (
<input
value={value}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
/>
);
};
const Form = () => {
const [value, setValue] = React.useState('');
return (
<ControlledInput
type="text"
placeholder="Insert some text here..."
value={value}
onValueChange={setValue}
/>
);
};
ReactDOM.render(<Form />, document.getElementById('root'));
CountDown
- title: CountDown
- tags: components,state,advanced
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 thepaused
andover
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 thetick
method every second via the use ofsetInterval()
and useclearInterval()
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.
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>
);
};
ReactDOM.render(
<CountDown hours={1} minutes={45} />,
document.getElementById('root')
);
DataList
- title: DataList
- tags: components,beginner
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 indata
as a<li>
element with an appropriatekey
.
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>;
};
const names = ['John', 'Paul', 'Mary'];
ReactDOM.render(<DataList data={names} />, document.getElementById('root'));
ReactDOM.render(
<DataList data={names} isOrdered />,
document.getElementById('root')
);
DataTable
- title: DataTable
- tags: components,beginner
Renders a table with rows dynamically created from an array of primitives.
- Render a
<table>
element with two columns (ID
andValue
). - Use
Array.prototype.map()
to render every item indata
as a<tr>
element with an appropriatekey
.
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>
);
};
const people = ['John', 'Jesse'];
ReactDOM.render(<DataTable data={people} />, document.getElementById('root'));
FileDrop
- title: FileDrop
- tags: components,input,state,effect,event,advanced
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 thedrag
andfilename
variables, initialized tofalse
and''
respectively. - The variables
dragCounter
anddrag
are used to determine if a file is being dragged, whilefilename
is used to store the dropped file's name. - Create the
handleDrag
,handleDragIn
,handleDragOut
andhandleDrop
methods to handle drag and drop functionality. handleDrag
prevents the browser from opening the dragged file,handleDragIn
andhandleDragOut
handle the dragged file entering and exiting the component, whilehandleDrop
handles the file being dropped and passes it toonDrop
.- Use the
useEffect()
hook to handle each of the drag and drop events using the previously created methods.
.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;
}
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>
);
};
ReactDOM.render(
<FileDrop onDrop={console.log} />,
document.getElementById('root')
);
LimitedTextarea
- title: LimitedTextarea
- tags: components,state,callback,event,beginner
Renders a textarea component with a character limit.
- Use the
useState()
hook to create thecontent
state variable and set its value to that ofvalue
prop, trimmed down tolimit
characters. - Create a method
setFormattedContent
, which trims the content down tolimit
characters and memoize it, using theuseCallback()
hook. - Bind the
onChange
event of the<textarea>
to callsetFormattedContent
with the value of the fired event.
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>
</>
);
};
ReactDOM.render(
<LimitedTextarea limit={32} value="Hello!" />,
document.getElementById('root')
);
LimitedWordTextarea
- title: LimitedWordTextarea
- tags: components,input,state,callback,effect,event,intermediate
Renders a textarea component with a word limit.
- Use the
useState()
hook to create a state variable, containingcontent
andwordCount
, using thevalue
prop and0
as the initial values respectively. - Use the
useCallback()
hooks to create a memoized function,setFormattedContent
, that usesString.prototype.split()
to turn the input into an array of words. - Check if the result of applying
Array.prototype.filter()
combined withBoolean
has alength
longer thanlimit
and, if so, trim the input, otherwise return the raw input, updating state accordingly in both cases. - Use the
useEffect()
hook to call thesetFormattedContent
method on the value of thecontent
state variable during the initial render. - Bind the
onChange
event of the<textarea>
to callsetFormattedContent
with the value ofevent.target.value
.
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>
</>
);
};
ReactDOM.render(
<LimitedWordTextarea limit={5} value="Hello there!" />,
document.getElementById('root')
);
Loader
- title: Loader
- tags: components,beginner
Renders a spinning loader component.
- Render an SVG, whose
height
andwidth
are determined by thesize
prop. - Use CSS to animate the SVG, creating a spinning animation.
.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;
}
}
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>
);
};
ReactDOM.render(<Loader size={24} />, document.getElementById('root'));
Mailto
- title: Mailto
- tags: components,beginner
Renders a link formatted to send an email (mailto:
link).
- Use the
email
,subject
andbody
props to create a<a>
element with an appropriatehref
attribute. - Use
encodeURIcomponent
to safely encode thesubject
andbody
into the link URL. - Render the link with
children
as its content.
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>;
};
ReactDOM.render(
<Mailto email="foo@bar.baz" subject="Hello & Welcome" body="Hello world!">
Mail me!
</Mailto>,
document.getElementById('root')
);
MappedTable
- title: MappedTable
- tags: components,array,object,intermediate
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()
andArray.prototype.reduce()
to produce afilteredData
array, containing all objects with the keys specified inpropertyNames
. - Render a
<table>
element with a set of columns equal to the amount of values inpropertyNames
. - Use
Array.prototype.map()
to render each value in thepropertyNames
array as a<th>
element. - Use
Array.prototype.map()
to render each object in thefilteredData
array as a<tr>
element, containing a<td>
for each key in the object.
This component does not work with nested objects and will break if there are nested objects inside any of the properties specified in propertyNames
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>
);
};
const people = [
{ name: 'John', surname: 'Smith', age: 42 },
{ name: 'Adam', surname: 'Smith', gender: 'male' }
];
const propertyNames = ['name', 'surname', 'age'];
ReactDOM.render(
<MappedTable data={people} propertyNames={propertyNames} />,
document.getElementById('root')
);
Modal
- title: Modal
- tags: components,effect,intermediate
Renders a Modal component, controllable through events.
- Define
keydownHandler
, a method which handles all keyboard events and is used to callonClose
when theEsc
key is pressed. - Use the
useEffect()
hook to add or remove thekeydown
event listener to thedocument
, callingkeydownHandler
for every event. - Add a styled
<span>
element that acts as a close button, callingonClose
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 theisVisible
attribute.
.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);
}
}
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>
);
};
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.render(<App />, document.getElementById('root'));
MultiselectCheckbox
- title: MultiselectCheckbox
- tags: components,input,state,array,intermediate
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 thedata
state variable and use theoptions
prop to initialize its value. - Create a
toggle
function that uses the spread operator (...
) andArray.prototype.splice()
to update thedata
state variable and call theonChange
callback with anychecked
options. - Use
Array.prototype.map()
to map thedata
state variable to individual<input type="checkbox">
elements, each one wrapped in a<label>
, binding theonClick
handler to thetoggle
function.
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>
))}
</>
);
};
const options = [{ label: 'Item One' }, { label: 'Item Two' }];
ReactDOM.render(
<MultiselectCheckbox
options={options}
onChange={data => {
console.log(data);
}}
/>,
document.getElementById('root')
);
PasswordRevealer
- title: PasswordRevealer
- tags: components,input,state,beginner
Renders a password input field with a reveal button.
- Use the
useState()
hook to create theshown
state variable and set its value tofalse
. - When the
<button>
is clicked, executesetShown
, toggling thetype
of the<input>
between"text"
and"password"
.
const PasswordRevealer = ({ value }) => {
const [shown, setShown] = React.useState(false);
return (
<>
<input type={shown ? 'text' : 'password'} value={value} />
<button onClick={() => setShown(!shown)}>Show/Hide</button>
</>
);
};
ReactDOM.render(<PasswordRevealer />, document.getElementById('root'));
RippleButton
- title: RippleButton
- tags: components,state,effect,intermediate
Renders a button that animates a ripple effect when clicked.
- Use the
useState()
hook to create thecoords
andisRippling
state variables for the pointer's coordinates and the animation state of the button respectively. - Use a
useEffect()
hook to change the value ofisRippling
every time thecoords
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 resetcoords
whenever theisRippling
state variable isfalse.
- Handle the
onClick
event by updating thecoords
state variable and calling the passed callback.
.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;
}
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>
);
};
ReactDOM.render(
<RippleButton onClick={e => console.log(e)}>Click me</RippleButton>,
document.getElementById('root')
);
Select
- title: Select
- tags: components,input,beginner
Renders an uncontrolled <select>
element that uses a callback function to pass its value to the parent component.
- Use the the
selectedValue
prop as thedefaultValue
of the<select>
element to set its initial value.. - Use the
onChange
event to fire theonValueChange
callback and send the new value to the parent. - Use
Array.prototype.map()
on thevalues
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 thevalue
of the item and the second one is the displayed text for it.
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>
);
};
const choices = [
['grapefruit', 'Grapefruit'],
['lime', 'Lime'],
['coconut', 'Coconut'],
['mango', 'Mango'],
];
ReactDOM.render(
<Select
values={choices}
selectedValue="lime"
onValueChange={val => console.log(val)}
/>,
document.getElementById('root')
);
Slider
- title: Slider
- tags: components,input,beginner
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 theonValueChange
callback and send the new value to the parent.
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}
/>
);
};
ReactDOM.render(
<Slider onValueChange={val => console.log(val)} />,
document.getElementById('root')
);
StarRating
- title: StarRating
- tags: components,children,input,state,intermediate
Renders a star rating component.
- Define a component, called
Star
that will render each individual star with the appropriate appearance, based on the parent component's state. - In the
StarRating
component, use theuseState()
hook to define therating
andselection
state variables with the appropriate initial values. - Create a method,
hoverOver
, that updatesselected
according to the providedevent
, using the .data-star-id
attribute of the event's target or resets it to0
if called with anull
argument. - Use
Array.from()
to create an array of5
elements andArray.prototype.map()
to create individual<Star>
components. - Handle the
onMouseOver
andonMouseLeave
events of the wrapping element usinghoverOver
and theonClick
event usingsetRating
.
.star {
color: ##ff9933;
cursor: pointer;
}
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>
);
};
ReactDOM.render(<StarRating value={2} />, document.getElementById('root'));
Tabs
- title: Tabs
- tags: components,state,children,intermediate
Renders a tabbed menu and view component.
- Define a
Tabs
component that uses theuseState()
hook to initialize the value of thebindIndex
state variable todefaultIndex
. - Define a
TabItem
component and filterchildren
passed to theTabs
component to remove unnecessary nodes except forTabItem
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 updatesbindIndex
based on the clicked element.- Use
Array.prototype.map()
on the collected nodes to render the menu and view of the tabs, using the value ofbinIndex
to determine the active tab and apply the correctclassName
.
.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;
}
const TabItem = props => <div {...props} />;
const Tabs = ({ defaultIndex = 0, onTabClick, children }) => {
const [bindIndex, setBindIndex] = React.useState(defaultIndex);
const changeTab = newIndex => {
if (typeof onItemClick === 'function') onItemClick(itemIndex);
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>
);
};
ReactDOM.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>,
document.getElementById('root')
);
TagInput
- title: TagInput
- tags: components,input,state,intermediate
Renders a tag input field.
- Define a
TagInput
component and use theuseState()
hook to initialize an array fromtags
. - Use
Array.prototype.map()
on the collected nodes to render the list of tags. - Define the
addTagData
method, which will be executed when pressing theEnter
key. - The
addTagData
method callssetTagData
to add the new tag using the spread (...
) operator to prepend the existing tags and add the new tag at the end of thetagData
array. - Define the
removeTagData
method, which will be executed on clicking the delete icon in the tag. - Use
Array.prototype.filter()
in theremoveTagData
method to remove the tag using itsindex
to filter it out from thetagData
array.
.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;
}
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>
);
};
ReactDOM.render(
<TagInput tags={['Nodejs', 'MongoDB']} />,
document.getElementById('root')
);
TextArea
- title: TextArea
- tags: components,input,beginner
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 theonValueChange
callback and send the new value to the parent.
const TextArea = ({
cols = 20,
rows = 2,
defaultValue,
onValueChange,
...rest
}) => {
return (
<textarea
cols={cols}
rows={rows}
defaultValue={defaultValue}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
/>
);
};
ReactDOM.render(
<TextArea
placeholder="Insert some text here..."
onValueChange={val => console.log(val)}
/>,
document.getElementById('root')
);
Toggle
- title: Toggle
- tags: components,state,beginner
Renders a toggle component.
- Use the
useState()
hook to initialize theisToggleOn
state variable todefaultToggled
. - Render an
<input>
and bind itsonClick
event to update theisToggledOn
state variable, applying the appropriateclassName
to the wrapping<label>
.
.toggle input[type="checkbox"] {
display: none;
}
.toggle.on {
background-color: green;
}
.toggle.off {
background-color: red;
}
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>
);
};
ReactDOM.render(<Toggle />, document.getElementById('root'));
Tooltip
- title: Tooltip
- tags: components,state,children,beginner
Renders a tooltip component.
- Use the
useState()
hook to create theshow
variable and initialize it tofalse
. - Render a container element that contains the tooltip element and the
children
passed to the component. - Handle the
onMouseEnter
andonMouseLeave
methods, by altering the value of theshow
variable, toggling theclassName
of the tooltip.
.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;
}
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>
);
};
ReactDOM.render(
<Tooltip text="Simple tooltip">
<button>Hover me!</button>
</Tooltip>,
document.getElementById('root')
);
TreeView
- title: TreeView
- tags: components,object,state,recursion,advanced
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 theisToggled
state variable and give it the value of thetoggled
prop initially. - Render a
<span>
element and bind itsonClick
event to alter the component'sisToggled
state. - Determine the appearance of the component, based on
isParentToggled
,isToggled
,name
and checking forArray.isArray()
ondata
. - 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.
.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;
}
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>
);
};
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.render(
<TreeView data={data} name="data" />,
document.getElementById('root')
);
UncontrolledInput
- title: UncontrolledInput
- tags: components,input,intermediate
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 theonValueChange
callback and send the new value to the parent.
const UncontrolledInput = ({ defaultValue, onValueChange, ...rest }) => {
return (
<input
defaultValue={defaultValue}
onChange={({ target: { value } }) => onValueChange(value)}
{...rest}
/>
);
};
ReactDOM.render(
<UncontrolledInput
type="text"
placeholder="Insert some text here..."
onValueChange={console.log}
/>,
document.getElementById('root')
);
useAsync
- title: useAsync
- tags: hooks,state,reducer,advanced
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 thestate
variable and thedispatch
function. - Define an asynchronous
run
function that will run the provided callback,fn
, while usingdispatch
to updatestate
as necessary. - Return an object containing the properties of
state
(value
,error
andloading
) and therun
function.
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 };
};
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.render(<RandomImage />, document.getElementById('root'));
useClickInside
- title: useClickInside
- tags: hooks,effect,event,intermediate
Handles the event of clicking inside the wrapped component.
- Create a custom hook that takes a
ref
and acallback
to handle the'click'
event. - Use the
useEffect()
hook to append and clean up theclick
event. - Use the
useRef()
hook to create aref
for your click component and pass it to theuseClickInside
hook.
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);
};
});
};
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.render(
<ClickBox onClickInside={() => alert('click inside')} />,
document.getElementById('root')
);
useClickOutside
- title: useClickOutside
- tags: hooks,effect,event,intermediate
Handles the event of clicking outside of the wrapped component.
- Create a custom hook that takes a
ref
and acallback
to handle theclick
event. - Use the
useEffect()
hook to append and clean up theclick
event. - Use the
useRef()
hook to create aref
for your click component and pass it to theuseClickOutside
hook.
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);
};
});
};
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.render(
<ClickBox onClickOutside={() => alert('click outside')} />,
document.getElementById('root')
);
useComponentDidMount
- title: useComponentDidMount
- tags: hooks,effect,beginner
Executes a callback immediately after a component is mounted.
- Use
useEffect()
with an empty array as the second argument to execute the provided callback only once when the component is mounted. - Behaves like the
componentDidMount()
lifecycle method of class components.
const useComponentDidMount = onMountHandler => {
React.useEffect(() => {
onMountHandler();
}, []);
};
const Mounter = () => {
useComponentDidMount(() => console.log('Component did mount'));
return <div>Check the console!</div>;
};
ReactDOM.render(<Mounter />, document.getElementById('root'));
useComponentWillUnmount
- title: useComponentWillUnmount
- tags: hooks,effect,beginner
Executes a callback immediately before a component is unmounted and destroyed.
- Use
useEffect()
with an empty array as the second argument and return the provided callback to be executed only once before cleanup. - Behaves like the
componentWillUnmount()
lifecycle method of class components.
const useComponentWillUnmount = onUnmountHandler => {
React.useEffect(
() => () => {
onUnmountHandler();
},
[]
);
};
const Unmounter = () => {
useComponentWillUnmount(() => console.log('Component will unmount'));
return <div>Check the console!</div>;
};
ReactDOM.render(<Unmounter />, document.getElementById('root'));
useCopyToClipboard
- title: useCopyToClipboard
- tags: hooks,effect,state,callback,advanced
Copies the given text to the clipboard.
- Use the copyToClipboard snippet to copy the text to clipboard.
- Use the
useState()
hook to initialize thecopied
variable. - Use the
useCallback()
hook to create a callback for thecopyToClipboard
method. - Use the
useEffect()
hook to reset thecopied
state variable if thetext
changes. - Return the
copied
state variable and thecopy
callback.
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];
};
const TextCopy = props => {
const [copied, copy] = useCopyToClipboard('Lorem ipsum');
return (
<div>
<button onClick={copy}>Click to copy</button>
<span>{copied && 'Copied!'}</span>
</div>
);
};
ReactDOM.render(<TextCopy />, document.getElementById('root'));
useDebounce
- title: useDebounce
- tags: hooks,state,effect,intermediate
Debounces the given value.
- Create a custom hook that takes a
value
and adelay
. - Use the
useState()
hook to store the debounced value. - Use the
useEffect()
hook to update the debounced value every timevalue
is updated. - Use
setTimeout()
to create a timeout that delays invoking the setter of the previous state variable bydelay
ms. - Use
clearTimeout()
to clean up when dismounting the component. - This is particularly useful when dealing with user input.
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = React.useState(value);
React.useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value]);
return debouncedValue;
};
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.render(<Counter />, document.getElementById('root'));
useFetch
- title: useFetch
- tags: hooks,effect,state,intermediate
Implements fetch
in a declarative manner.
- Create a custom hook that takes a
url
andoptions
. - Use the
useState()
hook to initialize theresponse
anderror
state variables. - Use the
useEffect()
hook to asynchronously callfetch()
and update the state variables accordingly. - Return an object containing the
response
anderror
state variables.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
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.render(<ImageFetch />, document.getElementById('root'));
useInterval
- title: useInterval
- tags: hooks,effect,intermediate
Implements setInterval
in a declarative manner.
- Create a custom hook that takes a
callback
and adelay
. - Use the
useRef()
hook to create aref
for the callback function. - Use a
useEffect()
hook to remember the latestcallback
whenever it changes. - Use a
useEffect()
hook dependent ondelay
to set up the interval and clean up.
const useInterval = (callback, delay) => {
const savedCallback = React.useRef();
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
const Timer = props => {
const [seconds, setSeconds] = React.useState(0);
useInterval(() => {
setSeconds(seconds + 1);
}, 1000);
return <p>{seconds}</p>;
};
ReactDOM.render(<Timer />, document.getElementById('root'));
useMediaQuery
- title: useMediaQuery
- tags: hooks,state,effect,intermediate
Checks if the current environment matches a given media query and returns the appropriate value.
- Check if
window
andwindow.matchMedia
exist, returnwhenFalse
if not (e.g. SSR environment or unsupported browser). - Use
window.matchMedia()
to match the givenquery
, cast itsmatches
property to a boolean and store in a state variable,match
, using theuseState()
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
orwhenFalse
based on the value ofmatch
.
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;
};
const ResponsiveText = () => {
const text = useMediaQuery(
'(max-width: 400px)',
'Less than 400px wide',
'More than 400px wide'
);
return <span>{text}</span>;
};
ReactDOM.render(<ResponsiveText />, document.getElementById('root'));
useNavigatorOnLine
- title: useNavigatorOnLine
- tags: hooks,state,effect,intermediate
Checks if the client is online or offline.
- Create a function,
getOnLineStatus
, that uses theNavigatorOnLine
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.
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;
};
const StatusIndicator = () => {
const isOnline = useNavigatorOnLine();
return <span>You are {isOnline ? 'online' : 'offline'}.</span>;
};
ReactDOM.render(<StatusIndicator />, document.getElementById('root'));
usePersistedState
- title: usePersistedState
- tags: hooks,state,effect,advanced
Returns a stateful value, persisted in localStorage
, and a function to update it.
- Use the
useState()
hook to initialize thevalue
todefaultValue
. - Use the
useRef()
hook to create a ref that will hold thename
of the value inlocalStorage
. - Use 3 instances of the
useEffect()
hook for initialization,value
change andname
change respectively. - When the component is first mounted, use
Storage.getItem()
to updatevalue
if there's a stored value orStorage.setItem()
to persist the current value. - When
value
is updated, useStorage.setItem()
to store the new value. - When
name
is updated, useStorage.setItem()
to create the new key, update thenameRef
and useStorage.removeItem()
to remove the previous key fromlocalStorage
. - NOTE: The hook is meant for use with primitive values (i.e. not objects) and doesn't account for changes to
localStorage
due to other code. Both of these issues can be easily handled (e.g. JSON serialization and handling the'storage'
event).
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];
};
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.render(<MyApp />, document.getElementById('root'));
usePrevious
- title: usePrevious
- tags: hooks,state,effect,beginner
Stores the previous state or props.
- Create a custom hook that takes a
value
. - Use the
useRef()
hook to create aref
for thevalue
. - Use the
useEffect()
hook to remember the latestvalue
.
const usePrevious = value => {
const ref = React.useRef();
React.useEffect(() => {
ref.current = value;
});
return ref.current;
};
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.render(<Counter />, document.getElementById('root'));
useSSR
- title: useSSR
- tags: hooks,effect,state,memo,intermediate
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
andDocument.createElement()
to check if the code is running on the browser. - Use the
useState()
hook to define theinBrowser
state variable. - Use the
useEffect()
hook to update theinBrowser
state variable and clean up at the end. - Use the
useMemo()
hook to memoize the return values of the custom hook.
const isDOMavailable = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
);
const useSSR = (callback, delay) => {
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]
);
};
const SSRChecker = props => {
let { isBrowser, isServer } = useSSR();
return <p>{isBrowser ? 'Running on browser' : 'Running on server'}</p>;
};
ReactDOM.render(<SSRChecker />, document.getElementById('root'));
useTimeout
- title: useTimeout
- tags: hooks,effect,intermediate
Implements setTimeout
in a declarative manner.
- Create a custom hook that takes a
callback
and adelay
. - Use the
useRef()
hook to create aref
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.
const useTimeout = (callback, delay) => {
const savedCallback = React.useRef();
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setTimeout(tick, delay);
return () => clearTimeout(id);
}
}, [delay]);
};
const OneSecondTimer = props => {
const [seconds, setSeconds] = React.useState(0);
useTimeout(() => {
setSeconds(seconds + 1);
}, 1000);
return <p>{seconds}</p>;
};
ReactDOM.render(<OneSecondTimer />, document.getElementById('root'));
useToggler
- title: useToggler
- tags: hooks,state,callback,beginner
Provides a boolean state variable that can be toggled between its two states.
- Use the
useState()
hook to create thevalue
state variable and its setter. - Create a function that toggles the value of the
value
state variable and memoize it, using theuseCallback()
hook. - Return the
value
state variable and the memoized toggler function.
const useToggler = initialState => {
const [value, setValue] = React.useState(initialState);
const toggleValue = React.useCallback(() => setValue(prev => !prev), []);
return [value, toggleValue];
};
const Switch = () => {
const [val, toggleVal] = useToggler(false);
return <button onClick={toggleVal}>{val ? 'ON' : 'OFF'}</button>;
};
ReactDOM.render(<Switch />, document.getElementById('root'));
useUnload
- title: useUnload
- tags: hooks,effect,event,intermediate
Handles the beforeunload
window event.
- Use the
useRef()
hook to create a ref for the callback function,fn
. - Use the
useEffect()
hook andEventTarget.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.
const useUnload = fn => {
const cb = React.useRef(fn);
React.useEffect(() => {
const onUnload = cb.current;
window.addEventListener('beforeunload', onUnload);
return () => {
window.removeEventListener('beforeunload', onUnload);
};
}, [cb]);
};
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.render(<App />, document.getElementById('root'));