Search code examples
reactjsdebouncing

How to implement debounce in React textarea?


I try to add debounce to handleInputChange in my React component. When user types in textarea, is shouldn't constantly rerender (I check it by console.log(commentBody), but maybe I'am wrong, I'm not very experienced in React) but the problem is that it doesn't work properly. All variants I tried add delay to typing as well, see below.

import { debounce } from "lodash";
import ReviewCard from "../ReviewCard/ReviewCard";

const CommentInput = ({ postId, userId, onCommentChange, onCommentSubmit }) => {
  const dispatch = useDispatch();
  const [commentBody, setCommentBody] = useState("");
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submittedText, setSubmittedText] = useState("");
  const [submittedComments, setSubmittedComments] = useState([]);

  console.log(commentBody);

  const handleInputChange = (e) => {
    setCommentBody(e.target.value);
  };

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setIsSubmitting(true);
    const newComment = await dispatch(
      postComment({ body: commentBody, postId, userId })
    );

    setSubmittedText(commentBody);
    setIsSubmitting(false);
    onCommentChange("");

    onCommentSubmit(newComment.payload);
    setSubmittedComments([...submittedComments, newComment]);
    setCommentBody("");
  };

  return (
    <div className={st.add_comment}>
      {submittedComments.map((comment, index) => (
        <ReviewCard
          key={index}
          reviewerName={`@${comment.payload.user.username}`}
          commentary={submittedText}
        />
      ))}

      <Text type={"h3"}>
        Add <span className={st.highlight}> comment </span>
      </Text>
      <form className={st.add_comment_form} onSubmit={handleSubmit}>
        <textarea
          className={st.add_comment_input}
          value={commentBody}
          //   onChange={(e) => setCommentBody(e.target.value)}

          onChange={handleInputChange}
          placeholder="ENTER YOUR COMMENT"
          disabled={isSubmitting}
        />
        <Button type={"primary"} disabled={isSubmitting || !commentBody.trim()}>
          {isSubmitting ? "Posting..." : "Send"}
        </Button>
      </form>
    </div>
  );
};

export default CommentInput;

I tried to do so:

 const debouncedSave = debounce((value) => setCommentBody(value), 300); 

  const handleInputChange = (e) => {
    debouncedSave(e.target.value);
  };

but the problem is that it adds delay to typing also which is not what I need.


Solution

  • If you want to have debounce, you have 2 options.

    1. Controlled Input/Textarea

    If you want to keep your input controlled (controlling its value with state) You should use a separate state which stores the DEBOUNCED value. It would be like this:

    const MyComponent = () => {
        const [value, setValue] = useState("")
        const [debouncedValue, setDebouncedValue] = useState(value)
        const debounceRef = useRef<NodeJS.Timeout>(null);
    
        useEffect(() => {
            clearTimeout(debounceRef.current)
    
            debounceRef.current = setTimeout(() => setDebouncedValue(value), 300)
        }, [value])
    
        return <textarea value={value} />
    }
    
    2. Uncontrolled Input/Textarea (using onChange)

    You can also do this without using 2 states while our input is uncontrolled. Let's look at the example:

    const MyComponent = () => {
        const [debouncedValue, setDebouncedValue] = useState(value)
        const debounceRef = useRef<NodeJS.Timeout>(null);
    
        return (
           <textarea
               defaultValue="" // you can still use defaultValue if you want (optional)
               onChange={(e) => {
                   clearTimeout(debounceRef.current)
    
                   debounceRef.current = setTimeout(() => setDebouncedValue(value), 300)
               }} 
           />
        )
    }
    

    Both solutions work but I like the first one as I have more control over the input and I can change the main value anytime I want.

    BONUS: You can use @mantine/hooks library which exposes a log of custom hooks that can help you in such a place. It has 2 hooks that you can use for debouncing: useDebouncedState and useDebouncedValue. I use them in my real projects. You can have a look at the examples here: