Lam Nguyen

project

blog

Introduce

The previous part, we tell about textarea for handle input box. How about contentEditable?

then contenteditable is attribute make every thing can be editable. view more

The different is textarea only support text, it can multiple line but with plain text value. The contenteditable help you in create custom editors, rich text editor where user can input and format text.

This is make different, not plain text, now you control html input 🥹

Settings

If you follow from the previous, go to the repository react-input-chat-box and find the file InputContentEditable.tsx

function InputContentEditable() { return ( <div className={styles.inputContentEditable} ref={inputRef} contentEditable role="textbox" aria-label="Message..." /> ) }

The first problem is how div can display placeholder? We will not use placeholder property, we using aria-label and css for show place holder

.inputContentEditable { padding: 4px; word-break: break-word; white-space: pre-wrap; outline: none; &[aria-label]:empty:before { content: attr(aria-label); color: #765827; } }

Second problem is onChange not working. We can't using because div not have value, so the replace is using onInput. We store the html as state when need handle for some action like submit for format.

const [html, setHtml] = useState<string>('') const handleChange = (e: ChangeEvent<HTMLDivElement>) => { const { innerHTML, textContent } = e.currentTarget setHtml(innerHTML ?? '') if (textContent && textContent.length) { console.log(textContent) } } ... return ( <div className={styles.inputContentEditable} ref={inputRef} onInput={handleChange} ... />)

But how it can know the scrollbar should show. The div with contenteditable get problem with You can implement like part 1, but I implement another way is, have a parentRef will handle the height of input box. Because, I will implement it like some editor so this will helpful in future.

The different is when call the function? Because we change the html of div so, we use useEffect to this html, but cover for when the html clear and the div must clear all data in it. so the function for updateHeight will call later after reset complete for not make some bug with height.

const updateInputHeight = () => { if (inputRef.current && scrollRef.current) { scrollRef.current.style.overflow = 'hidden' scrollRef.current.style.height = `${inputRef.current.scrollHeight}px` if (inputRef.current.scrollHeight > MAX_CHAT_BOX_HEIGHT) { scrollRef.current.style.overflowY = 'scroll' scrollRef.current.style.height = `${MAX_CHAT_BOX_HEIGHT}px` } } } useEffect(() => { // reset input box if (inputRef.current && html !== inputRef.current.innerHTML) { inputRef.current.innerHTML = html } updateInputHeight() }, [html])

The final code:

import styles from './Input.module.css' import { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from 'react' const MAX_CHAT_BOX_HEIGHT = 100 function InputContentEditable() { const inputRef = useRef<HTMLDivElement>(null) const scrollRef = useRef<HTMLDivElement>(null) const [html, setHtml] = useState<string>('') const updateInputHeight = () => { if (inputRef.current && scrollRef.current) { scrollRef.current.style.overflow = 'hidden' scrollRef.current.style.height = `${inputRef.current.scrollHeight}px` if (inputRef.current.scrollHeight > MAX_CHAT_BOX_HEIGHT) { scrollRef.current.style.overflowY = 'scroll' scrollRef.current.style.height = `${MAX_CHAT_BOX_HEIGHT}px` } } } useEffect(() => { // reset input box if (inputRef.current && html !== inputRef.current.innerHTML) { inputRef.current.innerHTML = html } updateInputHeight() }, [html]) const handleChange = (e: ChangeEvent<HTMLDivElement>) => { const { innerHTML, textContent } = e.currentTarget setHtml(innerHTML ?? '') if (textContent && textContent.length) { console.log(textContent) } } const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() console.log('send') setHtml('') } } return ( <div ref={scrollRef} className={`${styles.inputWrapper} scrollbar`}> <div ref={inputRef} className={styles.inputContentEditable} contentEditable role="textbox" aria-label="Message..." onInput={handleChange} onKeyDown={handleKeyDown} /> </div> ) } export default InputContentEditable

Conclusion

After 2 part, I hope that give you simple idea about input box for different case

Made by Lam Nguyen. Contact with me @lamnguyen.