首页 > 解决方案 > React js textarea onchange不适用于值

问题描述

这是我的类组件,它是一个类似于 twitter 的自动建议文本区域。

import React, { Component, createRef } from 'react';
    import { TextArea, List, Triangle } from './Styled';
    import getCaretCoordinates from 'textarea-caret';
    
    class AutoCompleteTextArea extends Component {
        constructor(props) {
            super(props);
            this.state = {
                value: "",
                caret: {
                    top: 0,
                    left: 0,
                },
                isOpen: false,
                matchType: null,
                match: null,
                list: [],
                selectionEnd: null,
            };
            this.users = [
                { name: 'Bob', id: 'bobrm' },
                { name: 'Andrew', id: 'andrew_s' },
                { name: 'Simon', id: 'simon__a' },
            ];
            this.hashtagPattern = new RegExp(`([#])(?:(?!\\1)[^\\s])*$`);
            this.userPattern = new RegExp(`([@])(?:(?!\\1)[^\\s])*$`);
            this.textareaRef = createRef();
        }
    
        componentDidMount() {
            var caret;
            document.querySelector('textarea').addEventListener('input', (e) => {
                caret = getCaretCoordinates(e.target, e.target.selectionEnd);
                this.setState({ caret });
            });
        }
    
        keyDown = (e) => {
            this.autosize();
            const code = e.keyCode || e.which;
            // Down
            //if (code === 40) down()
            // Up
            //if (code === 38) up()
            // Enter
            //if (code === 13) onSelect()
        };
    
        onChange = (e) => {
            const { selectionEnd, value } = e.target;
            console.log(value);
            this.setState({ value });
            const userMatch = this.userPattern.exec(value.slice(0, selectionEnd));
            const hashtagMatch = this.hashtagPattern.exec(
                value.slice(0, selectionEnd)
            );
            if (hashtagMatch && hashtagMatch[0]) {
                this.setState({
                    matchType: hashtagMatch[1],
                    match: hashtagMatch[0],
                    selectionEnd,
                });
                this.suggest(hashtagMatch[0], hashtagMatch[1]);
            } else if (userMatch && userMatch[0]) {
                this.setState({
                    matchType: userMatch[1],
                    match: userMatch[0],
                    selectionEnd,
                });
                this.suggest(userMatch[0], userMatch[1]);
            } else {
                this.setState({
                    match: null,
                    matchType: null,
                    isOpen: false,
                });
            }
        };
    
        suggest = (match, matchType) => {
            if (matchType === '#') {
                someRequest.then((res) => {
                    this.setState({
                        list: res.body,
                        isOpen: res.body.length !== 0,
                    });
                });
            } else if (matchType === '@') {
                this.setState({
                    list: this.users,
                    isOpen: this.users.length !== 0,
                });
            }
        };
    
        autosize = () => {
            var el = document.getElementsByClassName('autoComplete')[0];
            setTimeout(function() {
                el.style.cssText = 'height:auto; padding:0';
                el.style.cssText = 'height:' + el.scrollHeight + 'px';
            }, 0);
        };
    
        hashtagClickHandler = (hashtag) => {
            const { selectionEnd, match, matchType, value } = this.state;
            const select = matchType + hashtag;
    
            // It's replace value text
            const pre = value.substring(0, selectionEnd - match.length) + select;
            const next = value.substring(selectionEnd);
            const newValue = pre + next;
            console.log(newValue);
            this.setState({ isOpen: false, value: newValue });
            this.textareaRef.current.selectionEnd = pre.length;
        };
    
        render() {
            return (
                <>
                    <TextArea
                        id="postText"
                        name="postText"
                        placeholder="What's on your mind ? ..."
                        maaxLength={255}
                        className="autoComplete"
                        onKeyDown={this.keyDown}
                        onChange={this.onChange}
                        value={this.state.value}
                        ref={this.textareaRef}
                    />
                    {this.state.isOpen && (
                        <List top={this.state.caret.top}>
                            <Triangle left={this.state.caret.left} />
                            {this.state.matchType === '#'
                                ? this.state.list.map((hashtag, index) => (
                                      <button
                                          key={'hashtag' + index}
                                          className="listItem"
                                          onClick={() =>
                                              this.hashtagClickHandler(
                                                  hashtag,
                                                  index
                                              )
                                          }
                                      >
                                          <h5>{`#${hashtag}`}</h5>
                                      </button>
                                  ))
                                : this.state.list.map((user, index) => (
                                      <button
                                          key={'user' + index}
                                          className="listItem"
                                      >
                                          <h5>{user.name}</h5>
                                          <p>{user.id}</p>
                                      </button>
                                  ))}
                        </List>
                    )}
                </>
            );
        }
    }
    
    export default AutoCompleteTextArea;

当我的 TextArea 上同时具有 value 和 onChange 道具时,它不会触发 onChange 函数。但是,如果我删除 value 道具,它将被解雇。实际上,我需要 onChange,因为当用户单击建议的主题标签之一时,我将更改该值。我的 TextArea 只是一个这样的样式组件:

export const TextArea = styled.textarea`
    float: left;
    width: 450px;
    font-size: 16px;
    font-weight: lighter;
    margin: 22px 0 0 20px;
    border: none;
    &::placeholder {
        color: ${(props) => props.theme.textGrayLight};
    }
    font-family: ${(props) => props.theme.mainFont};
    position: relative;
    resize: none;

    @media ${(props) => props.mediaHD} {
        width: 250px;
    }
`;

标签: javascripthtmlreactjs

解决方案


问题是,输入不能有多个input事件监听器。既然您已经有一个输入侦听器(the onChange),为什么不将插入符号设置移至此:

onChange = (e) => {
    const { selectionEnd, value } = e.target;        
    const caret = getCaretCoordinates(e.target, selectionEnd);
    this.setState({ caret, value  });

并删除didMount覆盖侦听器的 :

componentDidMount() {
            var caret;
            document.querySelector('textarea').addEventListener('input', (e) => {
                caret = getCaretCoordinates(e.target, e.target.selectionEnd);
                this.setState({ caret });
            });
        }

这是一个沙盒


推荐阅读