How to limit Max Length of Draft js
Asked Answered
I

5

20

How to limit max characters in draft js?

I can get length of the state like that, but how to stop updating component?

var length = editorState.getCurrentContent().getPlainText('').length;
Impend answered 4/9, 2017 at 21:30 Comment(0)
C
25

You should define handleBeforeInput and handlePastedText props. In handler-functions, you check the length of current content + length of pasted text and if it reaches the maximum you should return 'handled' string.

UPD 21.03.2018: Upgraded to the last versions of react/react-dom (16.2.0) and Draft.js (0.10.5).

Working example - https://jsfiddle.net/Ln1hads9/11/

const {Editor, EditorState} = Draft;

const MAX_LENGTH = 10;

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty()
    };
  }
  render() {
    return (
      <div className="container-root">
        <Editor
          placeholder="Type away :)"
          editorState={this.state.editorState}
          handleBeforeInput={this._handleBeforeInput}
          handlePastedText={this._handlePastedText}
          onChange={this._handleChange}
        />
      </div>
    );
  }

  _getLengthOfSelectedText = () => {
    const currentSelection = this.state.editorState.getSelection();
    const isCollapsed = currentSelection.isCollapsed();

    let length = 0;

    if (!isCollapsed) {
      const currentContent = this.state.editorState.getCurrentContent();
      const startKey = currentSelection.getStartKey();
      const endKey = currentSelection.getEndKey();
      const startBlock = currentContent.getBlockForKey(startKey);
      const isStartAndEndBlockAreTheSame = startKey === endKey;
      const startBlockTextLength = startBlock.getLength();
      const startSelectedTextLength = startBlockTextLength - currentSelection.getStartOffset();
      const endSelectedTextLength = currentSelection.getEndOffset();
      const keyAfterEnd = currentContent.getKeyAfter(endKey);
      console.log(currentSelection)
      if (isStartAndEndBlockAreTheSame) {
        length += currentSelection.getEndOffset() - currentSelection.getStartOffset();
      } else {
        let currentKey = startKey;

        while (currentKey && currentKey !== keyAfterEnd) {
          if (currentKey === startKey) {
            length += startSelectedTextLength + 1;
          } else if (currentKey === endKey) {
            length += endSelectedTextLength;
          } else {
            length += currentContent.getBlockForKey(currentKey).getLength() + 1;
          }

          currentKey = currentContent.getKeyAfter(currentKey);
        };
      }
    }

    return length;
  }

  _handleBeforeInput = () => {
    const currentContent = this.state.editorState.getCurrentContent();
    const currentContentLength = currentContent.getPlainText('').length;
    const selectedTextLength = this._getLengthOfSelectedText();

    if (currentContentLength - selectedTextLength > MAX_LENGTH - 1) {
      console.log('you can type max ten characters');

      return 'handled';
    }
  }

  _handlePastedText = (pastedText) => {
    const currentContent = this.state.editorState.getCurrentContent();
    const currentContentLength = currentContent.getPlainText('').length;
    const selectedTextLength = this._getLengthOfSelectedText();

    if (currentContentLength + pastedText.length - selectedTextLength > MAX_LENGTH) {
      console.log('you can type max ten characters');

      return 'handled';
    }
  }

  _handleChange = (editorState) => {
    this.setState({ editorState });
  }
}

ReactDOM.render(<Container />, document.getElementById('react-root'))
Curry answered 6/9, 2017 at 9:25 Comment(7)
what about a use case for highlighting some of the current content and pasting to overwrite the highlighted content?Hornswoggle
@devonJS Good point, thanks! This case was not provided initially. I updated the answer, currently, we check the length of selected text when we paste some content to the editor.Curry
@MikhailShabrikov - you probably want to check selection length when inserting single character as well. Currently you not allowing to select all text and press a key, but allow for paste.Petrosal
@codename Thanks, good point. I fixed it, please, check the updated fiddle.Curry
@MikhailShabrikov you have also created few constants that are not used later (e.g. isBackward) – any valid reason for that?Petrosal
Yes, you are right some of the variables are not used. I cleaned the code of the example.Curry
Can someone make PR to Draft.js to have one property like maxlength. That bunch of methods is ridiculous way to limit wiswyg input length.Impend
H
4

Mikhail's methods are correct, but the handler return value is not. 'not_handled' is a fall-through case that allows the Editor component to process the input normally. In this case, we want to stop the Editor from processing input.

In older versions of DraftJS, it looks like the presence of a string evaluated to 'true' in the handling code, and so the above code behaved correctly. In later versions of DraftJS, the above fiddle doesn't work - I don't have the reputation to post more that one Fiddle here, but try Mikhail's code with v0.10 of DraftJS to replicate.

To correct this, return 'handled' or true when you don't want the Editor to continue handling the input.

Fiddle with corrected return values

For example,

_handleBeforeInput = () => {
  const currentContent = this.state.editorState.getCurrentContent();
  const currentContentLength = currentContent.getPlainText('').length

  if (currentContentLength > MAX_LENGTH - 1) {
    console.log('you can type max ten characters');
    return 'handled';
  }
}

See the DraftJS docs on Cancelable Handlers for more.

Heterocyclic answered 17/10, 2017 at 14:21 Comment(0)
H
2

This is a bit of an old thread, but thought I would share a solution for anyone else facing the problem of character limit and behaviour while pasting text...

Put together something pretty quickly based on the above code by Mikhail to handle this use case which works for me - although I haven't done any work on trying to optimise it.

Basically the handle pasted text looks like so:

      const __handlePastedText = (pastedText: any) => {
        const currentContent = editorState.getCurrentContent();
        const currentContentLength = currentContent.getPlainText('').length;
        const selectedTextLength = _getLengthOfSelectedText();
    
        if (currentContentLength + pastedText.length - selectedTextLength > MAX_LENGTH) {
            const selection = editorState.getSelection()
            const isCollapsed = selection.isCollapsed()
            const tempEditorState = !isCollapsed ? _removeSelection() : editorState
            _addPastedContent(pastedText, tempEditorState)
          return 'handled';
        }
        return 'not-handled'
      }

We have a helper function to handle the deletion of the selection prior to pasting new characters which returns the new editor state:

      const _removeSelection = () => {
        const selection = editorState.getSelection()
        const startKey = selection.getStartKey()
                const startOffset = selection.getStartOffset()
                const endKey = selection.getEndKey()
                const endOffset = selection.getEndOffset()
                if (startKey !== endKey || startOffset !== endOffset) {
                    const newContent = Modifier.removeRange(editorState.getCurrentContent(), selection, 'forward')
                    const tempEditorState = EditorState.push(
                        editorState,
                        newContent,
                        "remove-range"
                    )
                    setEditorState(
                        tempEditorState
                    )
                    return tempEditorState
                }
                return editorState
      }

and finally the function to add the pasted text with a limit:

      const _addPastedContent = (input: any, editorState: EditorState) => {
        const inputLength = editorState
        .getCurrentContent()
        .getPlainText().length;
        let remainingLength = MAX_LENGTH - inputLength;

        const newContent = Modifier.insertText(
            editorState.getCurrentContent(),
            editorState.getSelection(),
            input.slice(0,remainingLength)
        ); 
        setEditorState(
            EditorState.push(
                editorState,
                newContent,
                "insert-characters"
            )
        )
      }

Link to worked example: https://codesandbox.io/s/objective-bush-1h9x6

Hobbema answered 28/5, 2021 at 1:53 Comment(0)
J
2

As Mikhail mentioned, you need to handle typing and pasting text. Here are both handlers. Note the paste handler will preserve text that is not outside the limit

function handleBeforeInput(text: string, state: EditorState): DraftHandleValue {
    const totalLength = state.getCurrentContent().getPlainText().length + text.length;
    return totalLength > MAX_LENGTH ? 'handled' : 'not-handled';
  }

function handlePastedText(text: string, _: string, state: EditorState): DraftHandleValue {
    const overflowChars = text.length + state.getCurrentContent().getPlainText().length - MAX_LENGTH;

    if (overflowChars > 0) {
      if (text.length - overflowChars > 0) {
        const newContent = Modifier.insertText(
          state.getCurrentContent(),
          state.getSelection(),
          text.substring(0, text.length - overflowChars)
        );
        setEditorState(EditorState.push(state, newContent, 'insert-characters'));
      }
      return 'handled';
    } else {
      return 'not-handled';
    }
  }
Jumbuck answered 9/5, 2022 at 8:5 Comment(0)
W
0

Let's think about this for a second. What is called to make the changes? Your onChange, right? Good. We also know the length. Correct? We attact the "worker" which is the onChange:

const length = editorState.getCurrentContent().getPlainText('').length;

// Your onChange function:   
onChange(editorState) {
 const MAX_LENGTH = 10;
 const length = editorState.getCurrentContent().getPlainText('').length;

 if (length <= MAX_LENGTH) {
  this.setState({ editorState }) // or this.setState({ editorState: editorState })
 }
} else {
 console.log(`Sorry, you've exceeded your limit of ${MAX_LENGTH}`)
}

I have not tried this but my 6th sense says it works just fine.

Whang answered 25/12, 2017 at 11:42 Comment(1)
Not really, the job of onchange is only to setState, it will collide for any kind of change in the editorstate, not just extra characters.Giraudoux

© 2022 - 2024 — McMap. All rights reserved.