How to create awaitable prompt as React Component

How to create awaitable prompt as React Component

Motivation

JavaScript browser API has prompt() function which is a synchronized function for getting text input from user. We sometimes uses that kind of input UI components. However, the natively implemented UI component cannot be customized. I wanted to make it with customized UI and make it awaitable like const value = await prompt();.

Implementation

Like public react component libraries, I implemented use hook function. I’m exposing only the usePrompt() because I do not want developers to care about the UI implementation and want them to focus on using it as a capsulized feature.

TyepScript implementation.

import styles from ./style.module.scss
import { useState, useRef, useCallback } from react
import { createPortal } from react-dom

type Props = {
open: boolean
value: string
onChange: (value: string) => void
onClose: (value: string | null) => void
}

export function Prompt({
open,
value,
onChange: onValueChange,
onClose
}: Props) {
const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
onValueChange(e.target.value)
}, [onValueChange])
const onOkClick = useCallback(() => {
onClose(value)
}, [value, onClose])
const onCancelClick = useCallback(() => {
onClose(null)
}, [onClose])
return createPortal((
open && (
<div className={styles.cover}>
<div className={styles.frame}>
<div>
<input type=text value={value} onChange={onChange} />
</div>
<div>
<button onClick={onCancelClick}>CANCEL</button>
<button onClick={onOkClick}>OK</button>
</div>
</div>
</div>
)
), document.body)
}

export function usePrompt() {
const [open, setOpen] = useState<boolean>(false)
const [value, setValue] = useState<string>(“”)
const onCloseRef = useRef<(value: string | null) => void>()
const onClose = useCallback((value: string | null) => {
setOpen(false)
if (onCloseRef.current) {
onCloseRef.current(value)
}
}, [setOpen, onCloseRef])
const onChange = (value: string) => {
setValue(value)
}
return {
open: async (value: string) => {
setOpen(true)
setValue(value)
return new Promise<string|null>((resolve) => {
onCloseRef.current = (value: string | null) => {
resolve(value)
}
})
},
elem: (
<Prompt open={open} value={value} onClose={onClose} onChange={onChange}/>
)
}
}

Style in SASS

.cover {
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
height: 100dvh;
left: 0;
position: fixed;
top: 0;
width: 100dvw;
}

.frame {
background-color: white;
padding: 16px;
}

How to use

import { usePrompt } from ./Prompt
import { useState } from react

function App() {
const { open, elem } = usePrompt()
const [value, setValue] = useState<string>(“”)
const onOpenClick = () => {
open(Initial value).then((value) => {
setValue(value || cancelled)
})
}
return (
<>
<div>
<button onClick={onOpenClick}>Open prompt</button>
</div>
{value && <div>Input value: {value}</div>}
{elem}
</>
)
}

You can check my git repo if you want. Hope this helps!

Please follow and like us:
Pin Share