Skip to content

Instantly share code, notes, and snippets.

@bootrino
Created July 22, 2022 07:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bootrino/e2e5b4780a746dc4adaf7d725aabd085 to your computer and use it in GitHub Desktop.
Save bootrino/e2e5b4780a746dc4adaf7d725aabd085 to your computer and use it in GitHub Desktop.
How to do async debounce in TypeScript
import React, {useState, useEffect, useMemo} from "react";
import styled, {keyframes} from "styled-components";
import {toast} from "react-toastify";
import {CloseIcon} from "../Icons";
import {doFetch} from "../../utils";
import {Button as BSButton} from 'react-bootstrap';
import PhoneInput from 'react-phone-number-input'
import 'react-phone-number-input/style.css'
import {debounce} from "lodash";
const openModal = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`;
const Wrapper = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 900;
background: rgba(0, 0, 0, 0.7);
animation: ${openModal} 0.5s ease-in-out;
.edit-profile {
width: 580px;
border-radius: 4px;
background: ${(props) => props.theme.grey};
margin: 36px auto;
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.4), 0px 0px 4px rgba(0, 0, 0, 0.25);
}
.edit-profile img {
object-fit: cover;
}
.avatar {
margin-top: -40px;
margin-left: 20px;
}
div.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid ${(props) => props.theme.darkGrey};
}
h3 {
display: flex;
align-items: center;
}
form {
padding: 1rem;
}
input,
textarea {
width: 100%;
background: ${(props) => props.theme.black};
border: 1px solid ${(props) => props.theme.black};
padding: 0.6rem 1rem;
border-radius: 3px;
color: ${(props) => props.theme.primaryColor};
}
textarea {
height: 75px;
}
svg {
fill: ${(props) => props.theme.red};
height: 22px;
width: 22px;
margin-right: 1rem;
position: relative;
top: -1px;
}
@media screen and (max-width: 600px) {
.edit-profile {
width: 90%;
margin: 4rem auto;
}
}
@media screen and (max-width: 400px) {
background: rgba(0, 0, 0, 0.9);
}
`;
function asyncDebounce(func: Function, wait: number) {
const debounced = debounce((resolve, reject, args) => {
func(...args).then(resolve).catch(reject);
}, wait);
return (...args: any[]) =>
new Promise((resolve, reject) => {
debounced(resolve, reject, args);
});
}
const checkNumber = async (phoneNumber: string): Promise<boolean> => {
const url = `https://validate-phonenumber-7503.twil.io/validate?phone=${phoneNumber}`
try {
const result = await fetch(url);
const data = await result.json()
return data.success
} catch (e) {
alert(e)
return false
}
}
const debouncedCheckNumber: (...args: any[]) => Promise<unknown> = asyncDebounce(checkNumber, 1000);
const VerifyPhonenumberModal = () => {
const RECAPTCHA_PUBLIC_FRONTEND = "GETTHISFROMGOOGLE";
const [getPhoneNumberIsValid, setPhoneNumberIsValid] = useState<boolean>(false);
const [getCaptchaValue, setCaptchaValue] = useState<string | undefined>(undefined);
const [getPhoneNumber, setPhoneNumber] = useState<string>("");
const [getShowModal, setShowModal] = useState<boolean>(false);
const grecaptcha_element = React.useRef<HTMLDivElement>(null)
const onSubmit = async () => {
const url = `/auth/verifyphonenumber/${getPhoneNumber}/${getCaptchaValue}`
const result = await doFetch(url)
console.log(await result.json())
}
const closeModal = async () => {
setShowModal(false)
setPhoneNumber("")
setPhoneNumberIsValid(false)
setCaptchaValue("")
}
useEffect(() => {
// load recaptcha JS code
(window as any).captchaCallback = function (captchaValue: string) {
console.log('captchaCallback')
setCaptchaValue(captchaValue)
};
(window as any).captchaExpiredCallback = function() {
console.log('captchaExpiredCallback')
setCaptchaValue(undefined)
};
const scriptElement = document.createElement("script")
scriptElement.src = "https://www.google.com/recaptcha/api.js"
document.body.appendChild(scriptElement) as HTMLScriptElement
return () => {
document.body.removeChild(scriptElement);
(window as any).captchaCallback = undefined;
(window as any).captchaExpiredCallback = undefined;
}
}, [])
useEffect(() => {
const checkNumber = async () => {
const isValid = await debouncedCheckNumber(getPhoneNumber) as boolean
setPhoneNumberIsValid(isValid)
}
checkNumber()
}, [getPhoneNumber, setPhoneNumberIsValid])
useEffect(() => {
const handler = (e: CustomEvent) => {
console.log(e)
setShowModal(true)
}
document.addEventListener('SHOW_VERIFY_PHONE_NUMBER_MODAL', handler as EventListener)
return () => document.removeEventListener('SHOW_VERIFY_PHONE_NUMBER_MODAL', handler as EventListener)
}, [setShowModal])
useEffect(() => {
if (!grecaptcha_element) return
if (!grecaptcha_element.current) return
// we
// @ts-ignore
grecaptcha.render(grecaptcha_element.current, {
'sitekey': RECAPTCHA_PUBLIC_FRONTEND,
'callback': "captchaCallback",
'theme': "dark",
'expiredCallback': "captchaExpiredCallback",
});
}, [getShowModal])
if (!getShowModal) return null
return (
<Wrapper>
<div className="edit-profile">
<div style={{paddingTop: "10px", paddingBottom: "10px",}}>
<div className="modal-header">
<h3>
<CloseIcon onClick={closeModal}/>
<span>Verify Phone Number</span>
</h3>
</div>
<div style={{"textAlign": "center", "paddingTop": "1em", "marginBottom": "1em", "marginTop": "1em"}}>
<p>You must verify your phone number before you can render videos.</p>
</div>
<div className="container">
<PhoneInput
international
defaultCountry="US"
placeholder="Enter phone number"
value={getPhoneNumber}
onChange={(value) => {
if (value !== undefined) setPhoneNumber(value)
}}/>
<div style={{"textAlign": "center"}}>
{getPhoneNumberIsValid && <img
src="/images/greentick.png"
style={{"width": "48px", "marginTop": "10px"}}/>}
{getPhoneNumberIsValid && <p>looks like a valid number... now hit "send verify code"</p>}
</div>
<div style={{"width": "100%", "display": "flex", "justifyContent": "center", "marginBottom": "1em", "marginTop": "1em"}}>
<div ref={grecaptcha_element}/>
</div>
<div style={{"width": "100%", "display": "flex", "justifyContent": "center", "marginBottom": "1em", "marginTop": "1em"}}>
<BSButton
className="btn btn-secondary"
onClick={closeModal}>cancel</BSButton>
&nbsp;&nbsp;&nbsp;&nbsp;
<BSButton
disabled={(getCaptchaValue === undefined) || (!getPhoneNumberIsValid)}
className={((getCaptchaValue === undefined) || (!getPhoneNumberIsValid)) ? "btn btn-outline-light" : "btn btn-primary"}
onClick={onSubmit}>send verify code</BSButton>
</div>
</div>
</div>
</div>
</Wrapper>
);
};
export default VerifyPhonenumberModal;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment