Search code examples
reactjstypescriptantdreact-typescriptsuneditor

React Typescript tag details onclick not showing on the sun editor


I'm using my react typescript project for Ant design,

this is my conflict, I wanna click on the tag and its display to Sun editor content area so when I click the tag display the text area but it I cant to add on the sun editor any solution for this? Thanks

stazkblitz here

code here

import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Comment, Avatar, Form, Button, List, Input,Tag } from 'antd';
import moment from 'moment';
import 'suneditor/dist/css/suneditor.min.css';
import SunEditor from "suneditor-react";
const { TextArea } = Input;

const CommentList = ({ comments }) => (
  <List
    dataSource={comments}
    header={`${comments.length} ${comments.length > 1 ? 'replies' : 'reply'}`}
    itemLayout="horizontal"
    renderItem={props => <Comment {...props} />}
  />
);

const Editor = ({ onChange, onSubmit, submitting, value }) => (
  <>
    <Form.Item>
      <TextArea rows={4} onChange={onChange} value={value} />
    </Form.Item>
    <Form.Item>
      <Button htmlType="submit" loading={submitting} onClick={onSubmit} type="primary">
        Add Comment
      </Button>
    </Form.Item>
  </>
);

class App extends React.Component {
  state = {
    comments: [],
    submitting: false,
    value: '',
  };

  handleSubmit = () => {
    if (!this.state.value) {
      return;
    }

    this.setState({
      submitting: true,
    });

    setTimeout(() => {
      this.setState({
        submitting: false,
        value: '',
        comments: [
          ...this.state.comments,
          {
            author: 'Han Solo',
            avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
            content: <p>{this.state.value}</p>,
            datetime: moment().fromNow(),
          },
        ],
      });
    }, 1000);
  };

  handleChange = e => {
    this.setState({
      value: e.target.value,
    });
  };

  addTag = e => {
    const txt = e.target.innerHTML;
    this.setState(prevState => ({
      ...prevState,
      value: `${prevState.value} <${txt}> `,
    }));
  }

  render() {
    const { comments, submitting, value } = this.state;

    return (
      <>

       <div>
      <Tag onClick={this.addTag} color="magenta">First Name</Tag>
      <Tag onClick={this.addTag} color="red">Last Name</Tag>
      <Tag onClick={this.addTag} color="volcano">NIC</Tag>
      <Tag onClick={this.addTag} color="orange">FAX</Tag>
    
    </div>
        {comments.length > 0 && <CommentList comments={comments} />}
        <Comment
          avatar={
            <Avatar
              src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"
              alt="Han Solo"
            />
          }
          content={
            <Editor
              onChange={this.handleChange}
              onSubmit={this.handleSubmit}
              submitting={submitting}
              value={value}
            />
          }
        />

        <SunEditor

                                                        autoFocus={true}
                                                        width="100%"
                                                        height="150px"
                                                        onChange={this.handleChange}
                                                        onClick={this.onSubmit}
                                           /*       defaultValue={value}*/
                                                        setOptions={{
                                                            buttonList: [
                                                                // default
                                                                ['undo', 'redo'],
                                                                ['bold', 'underline', 'italic', 'list'],
                                                                ['table', 'link', 'image'],
                                                                ['fullScreen'],
                                                                ['codeView']
                                                            ]

                                                        }}
                                                    

                                                        setContents={value}
                                                    />

      </>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('container'));


  [1]: https://www.npmjs.com/package/suneditor-react
  [2]: https://stackblitz.com/edit/react-pqp2iu-ynivmu?file=index.js

Solution

  • UPDATE 13-MAY-2021

    using state in sun editor will trigger race problem between handleChange() and addTag(), there is a chance the state will be replaced by old state.

    To get rid of it, use the sun editor reference to manipulate the content.

    For adding new text and place it horizontally instead of vertically, they have a insertHTML() function which will respect the html content without adding new <p> in the first place.

    Updated Code: https://stackblitz.com/edit/react-pqp2iu-axacak?file=index.js

    • Create editorRef
    constructor() {
      super();
      this.editorRef = React.createRef();
    }
    
    • Apply the ref to sun editior
    <SunEditor ref={this.editorRef} ... />
    
    • Remove handleChange(), leave it to sun editor to handle it by itself
    • append text by using insertHTML()
    addTag = e => {
        const txt = e.target.innerHTML;
        this.editorRef.current.editor.insertHTML(`{${txt}}`);
    };
    

    OLD content

    < and > have special meaning in sun text editor which is representing html tags like <p> <a> etc and it will be hidden from the editor.

    Therefore, when you apply <Firtname> to the editor, it will disappear. To be able to display it, I suggest you to use mustache syntax {} which is used by SendGrid, Twilio template as well.

    Other than that, handleChange in sun text editor will return you the content directly so there is no need to get it from event target.

    Here is the forked version of yours with mustache template

    https://stackblitz.com/edit/react-pqp2iu-axacak?file=index.js

    handleChange = content => {
        console.log(content);
        this.setState({
          value: content
        });
    };
    
    addTag = e => {
        const txt = e.target.innerHTML;
        console.log(txt);
        this.setState(prevState => ({
          ...prevState,
          value: prevState.value.replace(/<\/p>$/, '') + `{${txt}}</p>` // remove the last </p>, append the new content, add back the </p> to avoid new line
        }));
    };