npm npm npm npm

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 the handleClick callback.
  • Use the isCollapsed prop in AccordionItem to determine its appearance and set an appropriate className.
  • Define an Accordion component that uses 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.
.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 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().
@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() 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.
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 appropriate href 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 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.
.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 the isCollapsed state variable with 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.
  • Determine the appearance of the content, based on isCollapsed and apply the appropriate className.
  • Update the value of the aria-expanded attribute based on isCollapsed 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 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.
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 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.
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 in data as a <li> element with an appropriate key.
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 and Value).
  • Use Array.prototype.map() to render every item in data as a <tr> element with an appropriate key.
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 the drag and filename variables, initialized 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, while 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.
.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 the content state variable and 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.
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, containing content and wordCount, using 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 and, if so, 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.
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 and width are determined by the size 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 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.
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() 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.

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 call 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.
.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}>
            &times;
          </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 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, each one wrapped in a <label>, binding the onClick handler to the toggle 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 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".
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 the coords and isRippling state variables 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.
.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 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.
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 the onValueChange 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 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 and the onClick event using setRating.
.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 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, using the value of binIndex to determine the active tab and apply the correct className.
.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 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.
.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 the onValueChange 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 the isToggleOn 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>.
.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 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.
.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 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.
.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>&nbsp;&nbsp;{name}: </strong> : <span>&nbsp;&nbsp;</span>}
      {isDataArray ? '[' : '{'}
      {!isToggled && '...'}
      {Object.keys(data).map((v, i, a) =>
        typeof data[v] === 'object' ? (
          <TreeView
            key={`${name}-${v}-${i}`}
            data={data[v]}
            isLast={i === a.length - 1}
            name={isDataArray ? null : v}
            isChildElement
            isParentToggled={isParentToggled && isToggled}
          />
        ) : (
          <p
            key={`${name}-${v}-${i}`}
            className={isToggled ? 'tree-element' : 'tree-element collapsed'}
          >
            {isDataArray ? '' : <strong>{v}: </strong>}
            {data[v]}
            {i === a.length - 1 ? '' : ','}
          </p>
        )
      )}
      {isDataArray ? ']' : '}'}
      {!isLast ? ',' : ''}
    </div>
  );
};
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 the onValueChange 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 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.
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 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.
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 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.
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 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.
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 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.
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 and options.
  • Use the useState() hook to initialize the response and error state variables.
  • Use the useEffect() hook to asynchronously call fetch() and update the state variables accordingly.
  • Return an object containing the response and error 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 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.
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 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.
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 the NavigatorOnLine 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 the value to defaultValue.
  • Use the useRef() hook to create a ref that will hold the name of the value in 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 localStorage.
  • 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 a ref for the value.
  • Use the useEffect() hook to remember the latest value.
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 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.
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 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.
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 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.
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 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.
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'));