Received NaN for the `children` attribute in React - javascript

I am new in React and I am building a type racer app. I am at this stage where I want to calculate to WPM (Words per minute) but for some reason the calculation returns 'NaN'. I have checked, each variable has the correct value and there are no empty values at the time of calculation.
Some pictures:
And here is the code for the App.js:
import React from 'react';
import { useState, useEffect } from 'react';
import './App.css';
import getTime from './CurrentTime.js';
const App = () => {
const [typeracertext, setTyperacertext] = useState("My name is Ruslan. ");
const [userText, setUserText] = useState("");
const [wholeText, setWholeText] = useState("");
const [startTyping, setStartTyping] = useState("");
const [endTyping, setEndTyping] = useState("");
var [countWords, setCountWords] = useState(0);
const wordsPerMinute = (startTime, endTime, words) => {
return 60 / ({endTime} - {startTime}) * words
}
const onChange = (e) => {
if (wholeText === "")
{
setStartTyping(getTime.getTime);
}
if (typeracertext.substring(0, wholeText.length+1).slice(-1) === e.target.value.slice(-1))
{
setUserText(e.target.value);
e.target.style.color = 'black';
if (typeracertext.substring(0, wholeText.length+1).slice(-1) === " ")
{
e.target.value = "";
setWholeText(wholeText + " ");
setCountWords(countWords + 1);
}
else
{
setWholeText(wholeText + ((e.target.value).slice(-1)));
}
}
else
{
e.target.style.color = 'red';
}
if (wholeText === typeracertext.substring(0, typeracertext.length-2))
{
setEndTyping(getTime.getTime);
e.target.value = "";
}
};
return (
<div className="wrapper">
<div className="box c">
<span className="userText">{wholeText}</span>{typeracertext.substring(wholeText.length, typeracertext.length)}
{endTyping != "" &&
<span className="wpmUser">{wordsPerMinute(startTyping, endTyping, countWords)}</span>}
);
}
export default App;
and the code for CurrentTime.js
import React from 'react';
const getTime = () => {
const current = new Date();
return(current.getHours()*60*60 + current.getMinutes()*60 + current.getSeconds());
}
export default {getTime};
EDIT: Here are also a proof that the values were passed:

You are not calling gettime in your set state. You are only pointing towards it
setStartTyping(getTime.getTime())
and
setEndTyping(getTime.getTime())
And why starttime and endtime are wrapped in {}. They are plain numbers.
Maybe you can do directly
const wordsPerMinute = (startTime, endTime, words) => {
return 60 / (endTime - startTime) * words
}

Related

Server Error Error: Too many re-renders. React limits the number of renders to prevent an infinite loop

I am trying to create a simple stopwatch app and getting the following error. I have tried the solutions for similar question yet have been unable to identify the real cause and solve it
here's my code
import React, { useState, useEffect } from "react"
function Watch() {
const [iter, setiter] = useState(0);
const [running, setRunning] = useState(false);
const [time, setTime] = useState("0:0:0");
const [start, setStart] = useState([]);
const [today, setToday] = useState([]);
useEffect(() => {
let interval;
if (iter == 0) {
var today = new Date()
setStart([today.getHours(), today.getMinutes(), today.getSeconds()])
}
if (running) {
interval = setInterval(() => {
setiter((previter) => previter + 10);
}, 10);
} else if (!running) {
clearInterval(interval);
}
var t = new Date()
setToday([t.getHours(), t.getMinutes(), t.getSeconds()])
return () => clearInterval(interval);
}, [running]);
return (
<div className="stopwatch">
<div className="numbers">
{setTime(running ? (String(today[0] - start[0]) + ':' + String(today[1] - start[1]) + ':' + String(today[2] - start[2])) : time)}
<span>{time}</span>
</div>
<div className="buttons">
<button onClick={() => setRunning(true)}>Start</button>
<button onClick={() => setRunning(false)}>Stop</button>
<button onClick={() => setTime("0:0:0")}>Reset</button>
</div>
</div>
);
};
export default Watch
Please note I am beginner at Next.js this is one of my first project.
Please consider this part again. The error comes from the below line:
<div className="numbers">
{setTime(running ? (String(today[0] - start[0]) + ':' + String(today[1] - start[1]) + ':' + String(today[2] - start[2])) : time)}
<span>{time}</span>
</div>
Please set time while component mounting using useEffect hook rather than now.

How can I return the numeric value of a React container instead of an object?

I'm trying to learn React and am working on a simple web app to implement what I've learned online so far.
I have a container called CardDatabase.js and all it returns is a single value. This value is called reduceArray and it's an array that has had the reduce() method called on it. When I render <CardDatabase.js /> the value appears in the browser and in the console as intended (in the screenshot, it's 86). However, when I try to assign the value of <CardDatabase /> to variable w inside of the calculate method in App.js, an object is returned instead of a numeric value (check the console in the screenshot).
How can I assign the numeric value of <CardDatabase /> to a variable so that I can use it to calculate some simple math?
I also tried using props and this.state but according to the console error I got and the React docs, we cannot modify the value of props? So when I tried something like:
const array = (rarityOfArray) => {
filteredArray = rarityOfArray.filter(item => item.key <= level)
for(let x = 0; x < filteredArray.length; x++) {
pushedArray.push(filteredArray[x].cardsRequiredToUpgrade)
console.log(pushedArray);
}
props.array = pushedArray.reduce((a, b) => a + b, 0);
console.log("Reduced - " + reducedArray);
return props.array
}
I got this error message: TypeError: Cannot assign to read only property 'array' of object '#<Object>'
Any help or direction would be greatly appreciated.
App.js
import React, {Component} from 'react';
// CSS
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
// Components
import CardRarityDropdown from './components/CardRarityDropdown';
import CardLevelDropdown from './components/CardLevelDropdown';
import CardAmountInput from './components/CardAmountInput';
import CardCalculationsButton from './components/CardCalculationsButton';
//Containers
import CardDatabase from './containers/cardDatabase/CardDatabase';
class App extends Component {
constructor() {
super();
this.state = {
cardRarity: "",
cardLevel: "",
amountOfCards: "",
}
this.setCardValues = this.setCardValues.bind(this);
}
setCardValues = (event) => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
calculate = () => {
let v = this.state.cardRarity;
console.log("v: " + v)
let w = <CardDatabase />
console.log("w: " + w)
let x = v - w;
console.log("x: " + x)
let y = this.state.amountOfCards;
console.log("y: " + y)
let z = x - y;
console.log("z: " + z)
return z
}
render () {
return (
<div>
<h1 id="superCellText">Clash Royale Card Calculator</h1>
<CardRarityDropdown
setCardValues={this.setCardValues}
cardRarity={this.state.cardRarity} />
<CardLevelDropdown
setCardValues={this.setCardValues}
cardRarity={this.state.cardRarity}
cardLevel={this.state.cardLevel} />
<CardDatabase
cardRarity={this.state.cardRarity}
cardLevel={this.state.cardLevel} />
<CardAmountInput
setCardValues={this.setCardValues}
cardRarity={this.state.cardRarity}
amountOfCards={this.state.amountOfCards} />
<CardCalculationsButton
calculate={this.calculate}
cardRarity={this.state.cardRarity} />
</div>
);
}
}
export default App;
CardDatabase.js
import common from './Common';
import rare from './Rare';
import epic from './Epic';
import legendary from './Legendary';
const CardDatabase = (props) => {
const card = props.cardRarity;
const level = props.cardLevel;
let filteredArray = [];
let pushedArray = [];
let reducedArray = [];
const array = (rarityOfArray) => {
filteredArray = rarityOfArray.filter(item => item.key <= level)
for(let x = 0; x < filteredArray.length; x++) {
pushedArray.push(filteredArray[x].cardsRequiredToUpgrade)
console.log(pushedArray);
}
reducedArray = pushedArray.reduce((a, b) => a + b, 0);
console.log("Reduced - " + reducedArray);
return reducedArray
}
if(card === "9586") {return array(common)}
if(card === "2586") {return array(rare)}
if(card === "386") {return array(epic)}
if(card === "36") {return array(legendary)}
if(card === "") {return null}
}
export default CardDatabase;
Folks on Reddit and Discord were giving me the same feedback, I shouldn't be using a component if said component isn't returning anything UI-related. Therefore, CardDatabase.js shouldn't be a component but a simple function instead.
I changed its name to cardDatabase.js so that React no longer considers it a component and instead of it receiving props, the function now has standard parameters.
Updated cardDatabase.js
import common from './Common';
import rare from './Rare';
import epic from './Epic';
import legendary from './Legendary';
const cardDatabase = (cardRarity, cardLevel) => {
let filteredArray = [];
let pushedArray = [];
let reducedArray = [];
const array = (rarityOfArray) => {
filteredArray = rarityOfArray.filter(item => item.key <= cardLevel)
for(let x = 0; x < filteredArray.length; x++) {
pushedArray.push(filteredArray[x].cardsRequiredToUpgrade)
console.log(pushedArray);
}
reducedArray = pushedArray.reduce((a, b) => a + b, 0);
console.log("Reduced - " + reducedArray);
return reducedArray
}
if(cardRarity === "9586") {return array(common)}
if(cardRarity === "2586") {return array(rare)}
if(cardRarity === "386") {return array(epic)}
if(cardRarity === "36") {return array(legendary)}
if(cardRarity === "") {return null}
}
export default cardDatabase;
In App.js I removed the <CardDatabase /> component and inside of my calculate method, simply assigned the value of variable w to my cardDatabase function with this.state.cardRarity and this.state.cardLevel as the arguments.
Updated App.js
import React, {Component} from 'react';
// CSS
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
// Components
import CardRarityDropdown from './components/CardRarityDropdown';
import CardLevelDropdown from './components/CardLevelDropdown';
import CardAmountInput from './components/CardAmountInput';
import CardCalculationsButton from './components/CardCalculationsButton';
//Containers
import cardDatabase from './containers/cardDatabase/cardDatabase.js';
class App extends Component {
constructor() {
super();
this.state = {
cardRarity: "",
cardLevel: "",
amountOfCards: "",
}
this.setCardValues = this.setCardValues.bind(this);
}
setCardValues = (event) => {
const {name, value} = event.target
this.setState({
[name]: value
})
}
calculate = () => {
let rarity = this.state.cardRarity;
let level = this.state.cardLevel;
console.log("v: " + rarity)
let w = cardDatabase(rarity, level);
console.log("w: " + w)
let x = rarity - w;
console.log("x: " + x)
let y = this.state.amountOfCards;
console.log("y: " + y)
let z = x - y;
console.log("z: " + z)
return z
}
render () {
return (
<div>
<h1 id="superCellText">Clash Royale Card Calculator</h1>
<CardRarityDropdown
setCardValues={this.setCardValues}
cardRarity={this.state.cardRarity} />
<CardLevelDropdown
setCardValues={this.setCardValues}
cardRarity={this.state.cardRarity}
cardLevel={this.state.cardLevel} />
<CardAmountInput
setCardValues={this.setCardValues}
cardRarity={this.state.cardRarity}
amountOfCards={this.state.amountOfCards} />
<CardCalculationsButton
calculate={this.calculate}
cardRarity={this.state.cardRarity} />
</div>
);
}
}
export default App;
Now w is returning a numerical value instead of an object and my app is finally functioning properly.

Height size change is not causing state update React Hooks

I'm trying to have my Carousel height resize dynamically upon change. However I can't seem trigger a state change purely from a childs height change.
Listening to children was pretty good, infact I'm not fully sure why it's not working.
The problem occurs when an error message is appended to a child within the carousel. It doesn't update.
Currently the best thing I know of to do is an interval...
Is there a better way?
import React, {useState, useEffect, useRef} from 'react';
import './Carousel.scss';
// Wrapped children components must be placed inside of <div> elements
const Carousel = ({slideTo, children}) => {
const [carouselStyle, setCarouselStyle] = useState({});
const activeRef = useRef(null);
const index = slideTo - 1
const newChildren = [];
children.map((d,i) => {
let addClass = (d.props.className !== undefined) ? d.props.className: ""
const newProps = {
key: i
};
if(i === index){
addClass += " active"
newProps.ref = activeRef;
}
newProps.className = addClass.trim();
const newChild = React.cloneElement(d, newProps);
newChildren.push(newChild);
return false
});
const carouselContainerStyle = {
left: (((slideTo * 100) - 100) * -1) + "%",
width: (newChildren.length * 100)+ "%"
}
useEffect(() => {
const interval = setInterval(function(){
console.log("int");
if(activeRef != null){
if(carouselStyle.height === undefined || carouselStyle.height !== activeRef.current.clientHeight){
setCarouselStyle({
height: activeRef.current.clientHeight,
});
}
}
},50)
return () => {
clearInterval(interval)
}
},[]);
useEffect(() => {
console.log("children update");
if(activeRef.current !== null){
setCarouselStyle({
height: activeRef.current.clientHeight,
});
}
},[slideTo,children]);
return (
<div className="carousel" style={carouselStyle}>
<div style={carouselContainerStyle} className="carousel-container">
{newChildren}
</div>
</div>
);
};
export default Carousel;
Implementation
<Carousel slideTo={slide}>
<div><SignIn/></div>
<div><SignUp/></div>
</Carousel>

How to replace an underscore with letter React Hook

I was trying to build a hanging man game and in order to make this game, the word has been replaced by underscores like for instance (hello) ==> _ _ _ _ _. So every time when the player clicks on the keyButton it should replace the underscore by the matched letter.
But one important thing to keep in mind is that it should not replace the underscore for instance like when a user clicks on e --> _ e _ _ _ and then clicks on l --> _ _ l l _. You see the letter e that matched was being replaced by underscore which is the case in my code. The magic happens in the showMatchedLetter function
import React, { useState, useEffect } from 'react';
import { fetchButton } from '../actions';
import axios from 'axios';
import 'babel-polyfill';
const App = () => {
const [word, setWord] = useState([]);
const [underscore, setUnderscore] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
const runEffect = async () => {
const result = await axios('src/api/api.js');
setData(result.data)
}
runEffect();
}, []);
const randomWord = () => {
const chosenWord = data[Math.floor(Math.random() * data.length)];
replaceLetter(chosenWord.word);
}
const replaceLetter = (string) => {
let getString = string;
setWord(getString);
let stringToUnderScore = getString.replace(/[a-z]/gi, '_');
setUnderscore(stringToUnderScore);
}
useEffect(() => {
const checkLetter = (event) => {
if(event.keyCode >= 65 && event.keyCode <= 90) {
checkMatchLetter(word, String.fromCharCode(event.keyCode).toLowerCase());
}
};
document.addEventListener('keydown', checkLetter);
return () => {
document.removeEventListener('keydown', checkLetter);
}
}, [word]);
const checkMatchLetter = (word, keyButton) => {
if(word == undefined) {
return;
} else {
for(let i = 0; i < word.length; i++) {
if(word[i] == keyButton) {
let index = i;
showMatchedLetter(word[i], index);
}
}
}
}
const showMatchedLetter = (letter, index) => {
console.log(letter, index);
let string = word;
string = setCharAt(string, index, letter);
console.log(string)
}
const setCharAt = (string, index, letter) => {
if(index > string.length - 1) return string;
console.log(string)
return underscore.substr(0, index) + letter + underscore.substr(index + 1);
}
return (
<div>
<p>{word}</p>
<p>{underscore}</p>
<button onClick={randomWord}></button>
</div>
)
}
export default App;
A way to approach this would be to just keep a running list of the letters that have already been guessed.
Then simply replace any letter that wasn't guessed with an underscore to get your "hidden" word each time a new letter is guessed.
The revealOnly function below can be implemented in any way you want, but what's important is that it takes the full word string and a list of letters to replace and then returns a new string that omits any non-guessed letters.
const phrase = 'Hello World'
function revealOnly(str, guessed=[]){
const regExpr = new RegExp(`[^${guessed.join("")}\\s]`, 'ig')
return str.replace(regExpr, '_')
}
console.log(revealOnly(phrase, [])) // no letters guessed
console.log(revealOnly(phrase, ['e', 'l'])) // some letters guessed
The following is a working demo in react to demonstrate how you might use this:
const { useState } = React
const App = () => {
const [phrase, setPhrase] = useState('Hello World')
const [guessed, setGuessed] = useState([])
function onGuessLetter(letter){
if(guessed.includes(letter)) return
// add letter to guessed array
setGuessed(prev => [...prev, letter])
}
function revealOnly(str, guessed=[]){
const regExpr = new RegExp(`[^${guessed.join("")}\\s]`, 'ig')
return str.replace(regExpr, '_')
}
return (
<div>
<div> {revealOnly(phrase, guessed)} </div>
<LetterPicker onPick={onGuessLetter} />
</div>
)
}
const LetterPicker = (props)=>{
const letters = []
for(let i = 0; i < 26; i++){
letters.push(String.fromCharCode(97+i))
}
return (
<div className={'letter-picker'}>
{letters.map(l => <div onClick={()=>props.onPick(l)}>{l}</div>)}
</div>
)
}
ReactDOM.render(<App/>, document.querySelector("#root"))
.letter-picker > div {
display : inline-block;
cursor : pointer;
padding : 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Keeping a running list of letters guessed could also be useful for checking if a word has been fully revealed (although you could just check for the existence of any _ as well). Similarly in traditional hangman fashion you can show the previously letters somewhere as well.
EDIT:
How do I check if the entire word is guessed.
Just check if the revealed word is equal to the original phrase.
const curr = revealOnly(phrase, ['h','e','l','w','o','r', 'd']
const isGuessed = curr === phrase
const { useState } = React
const App = () => {
const [phrase, setPhrase] = useState('Hello World')
const [guessed, setGuessed] = useState([])
function onGuessLetter(letter){
if(guessed.includes(letter)) return
// add letter to guessed array
setGuessed(prev => [...prev, letter])
}
function revealOnly(str, guessed=[]){
const regExpr = new RegExp(`[^${guessed.join("")}\\s]`, 'ig')
return str.replace(regExpr, '_')
}
const curr = revealOnly(phrase, guessed)
const isGuessed = curr === phrase
return (
<div>
<div> {curr} </div>
<div>Gussed: {isGuessed.toString()} </div>
<LetterPicker onPick={onGuessLetter} />
</div>
)
}
const LetterPicker = (props)=>{
const letters = []
for(let i = 0; i < 26; i++){
letters.push(String.fromCharCode(97+i))
}
return (
<div className={'letter-picker'}>
{letters.map(l => <div onClick={()=>props.onPick(l)}>{l}</div>)}
</div>
)
}
ReactDOM.render(<App/>, document.querySelector("#root"))
.letter-picker > div {
display : inline-block;
cursor : pointer;
padding : 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to limit Max Length of Draft js

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;
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'))
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.
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
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';
}
}
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.

Categories