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!
Top comments (0)