简体   繁体   中英

React js textarea onchange doesn't work with value

Here is my class component which is a twitter like auto-suggest text area.

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;

When I have both value and onChange props on my TextArea it doesn't fire the onChange function. But if I remove the value prop it will be fired. Actually I need the onChange because I'm going to change the value when the user clicks on one of the hashtags suggested. My TextArea is just a styled component like this:

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;
    }
`;

The problem is, that the inputs cannot have multiple input event listener. Since you already have a input listener(the onChange ), why do you not move the caret setting to this:

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

and remove the didMount , which overrides the listener:

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

Here is a sandbox .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM