When I press a key using keyEvent, then I call the function, it is below.
const GeneratedKey: FC<IGenerated> = (props) => {
const [keyBoard, setKeyBoard] = useState("")
const [arrayMovie, setArrayMovie] = useState<string[]>([])
const idPage = props.idpage
const nameMovie = data.results[idPage].title
const [idLetter, setIdLetter] = useState<number>(0)
const [indexArray, setIndexArray] = useState<number[]>([])
const arrayNameMovie = nameMovie.split(" ").join("").split("");
const getKey = (e: any) => {
console.log("test")
const key = e.key
let count = 0
for (let i = 65; i <= 90; i++) {
if (key.toUpperCase() == String.fromCharCode(i)) {
count = 1
} else if (key.toLowerCase() == "backspace") {
count = 10
}
}
if (count == 1) {
indexArray.sort(function (a: any, b: any) {
return a - b;
})
arrayMovie.splice(indexArray[idLetter], 1, key)
setIdLetter(idLetter + 1)
} else if (count == 10) {
if (idLetter >= 1) {
setIdLetter(idLetter - 1)
arrayMovie.splice(indexArray[idLetter], 1, "")
}
}
setKeyBoard(key)
document.removeEventListener("keydown", getKey);
}
useEffect(() => {
for (let i = 3; i >= 0; i--) {
const randomIndex = Math.floor(Math.random() * arrayNameMovie.length)
arrayNameMovie.splice(randomIndex, 1, " ")
indexArray.push(randomIndex)
}
setIdLetter(indexArray[0])
setArrayMovie(arrayNameMovie)
}, [])
document.addEventListener("keydown", getKey)
return (
<div className="down__word">
{arrayMovie.map((letter: any) =>
<LettersView letters={letter} props={undefined} />
)}
</div>
)
}
In fact, it should be called once, but it fires twice, as you can see from console.log();
How can I fix this, I can also show other files with code, but this is unlikely to help
This is due to your component may get rendered twice (due to change of props or any external reason). Moreover I think this is not the correct way to handle the event listener in FC. You should consider the useEffect for registration / un-registration of event listener.
useEffect(() => {
const handler = () => {};
document.addEventListener('keydown', handler);
return () => {
document.removeEventListener('keydown', handler);
}
}, [deps]); // if you have any changing dependency else just remove the array to execute the UseEffect everytime.
This will ensure that you have registered the event only once in your code.
Refer https://reactjs.org/docs/hooks-effect.html
Related
I have the following code that highlights all the elements that have the same 4 classes, position 0,1 3 and 4.
I have written it in vanilla js and React, the vanilla version works as expected but the react implementation works in a weird way.
Sometimes works, sometimes doesn't, it's like the onExit event is not being called at the right time but just a little bit after so that the prev variable which stores the last hovered item, has another value and didn't had the chance to remove the highlight class.
It's worth saying that this is being used in a Chrome Extension to highlight items on a webpage so everything that is highlighted is outise the react tree, when someone clicks my extension, the following code registers the event on the active tab.
There is also a onClick event which I didn't include to capture all the elements into a variable and then display some tools (which are React components) showing those elments and for that I need to somehow make the React to properly capture the onExit event on time or to connect the vanilla code to some React components to get the captured elements.
// vanilla script, first one
export const initEvents: any = () => {
let _accountantUtils = function () {
this.currClasses = [];
this.selected = [];
if (this.constructor.instance) return this.constructor.instance;
this.constructor.instance = this;
let prev;
this.initOnHover = () => {
const handler = (event) => {
if (event.target === document.body || (prev && prev === event.target)) {
return;
}
if (prev) {
this.onExit(prev);
prev.className = prev.className.replace(/\bh3-highlight\b/, "");
prev = undefined;
}
if (event.target) {
this.onHover(event.target);
prev = event.target;
prev.className += " h3-highlight";
}
}
if (document.body.addEventListener) {
document.body.addEventListener("mouseover", handler);
} else {
alert("browser not supported");
return;
}
};
this.onHover = function (eventEl) {
let classes = [
eventEl.classList[0],
eventEl.classList[1],
eventEl.classList[3],
eventEl.classList[4],
];
let selector = `.${classes.join(".")}`;
const elsWithClasses = Array.from(document.querySelectorAll(selector));
elsWithClasses.map((el) => el.classList.add("highlight"));
this.currClasses = classes;
};
this.onExit = function (eventEl) {
let classes = [
eventEl.classList[0],
eventEl.classList[1],
eventEl.classList[3],
eventEl.classList[4],
];
let selector = `.${classes.join(".")}`;
const elsWithClasses = Array.from(document.querySelectorAll(selector));
elsWithClasses.map((el) => el.classList.remove("highlight"));
this.currClasses = [];
};
};
const accountantUtils = new _accountantUtils();
return accountantUtils;
}
// then init it this this where it's imported:
const accountantUtils = initEvents();
accountantUtils.initOnHover();
const root = ReactDOM.createRoot(document.querySelector("#h3-app") as HTMLElement);
// React implementation
let prev = null;
export const HoverListener = function () {
useEffect(() => {
initOnHover(onExit, onHover);
}, []);
const initOnHover = function (onHover, onExit) {
if (document.body.addEventListener) {
eventListener = document.body.addEventListener('mouseover', handler);
}
else {
alert("browser not supported")
return;
}
function handler(event) {
if (event.target === document.body ||
(prev && prev === event.target)) {
return;
}
if (prev) {
if (onExit) onExit(prev)
prev.className = prev.className.replace(/\bh3-highlight\b/, '');
prev = undefined;
}
if (event.target) {
if (onHover) onHover(event.target)
prev = event.target;
prev.className += " h3-highlight";
}
}
}
const onExit = function (eventEl) {
let classes = [
eventEl.classList[0],
eventEl.classList[1],
eventEl.classList[3],
eventEl.classList[4],
];
let selector = `.${classes.join(".")}`;
const elsWithClasses = Array.from(document.querySelectorAll(selector));
elsWithClasses.map(el => el.classList.remove("h3-highlight"));
}
const onHover = function (eventEl) {
let classes = [
eventEl.classList[0],
eventEl.classList[1],
eventEl.classList[3],
eventEl.classList[4],
];
let selector = `.${classes.join(".")}`;
const elsWithClasses = Array.from(document.querySelectorAll(selector));
elsWithClasses.map(el => el.classList.add("h3-highlight"));
}
return (
<div>
<h1>Listener...</h1>
</div>
);
}
React implementation
Vanilla code
I have an input in which the user enters an expression of this form:
"TERM":"MATCH_TERM"
I would like that when the user enters a quote in the input, a second quote of the same type is added (the user can use single and double quotes : " or ') and that the mouse cursor is placed between the two quotes that have just been created.
My code is in ReactJs.
I managed to automatically add a second quote when the user enters a first one, at the right place in the string. But I can't figure out how to then move my mouse cursor between the two new quotes.
To make my input component do this, I wrote the following code:
(I tried to simplify the code but normally it is reproducible)
import * as React from "react";
import { useEffect, useState, useRef } from "react";
const QuoteInput: React.FC = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputChoose, setInputChoose] = useState("");
const [previousInputChoose, setPreviousInputChoose] = useState("");
const [testQuoteAddition, setTestQuoteAddition] = useState(false);
const [enterSingleQuote, setEnterSingleQuote] = useState(false);
const [enterDoubleQuote, setEnterDoubleQuote] = useState(false);
const inputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
setPreviousRequestChoose(requestChoose);
const enteredRequest = event.target.value;
setRequestChoose(enteredRequest);
setTestQuoteAddition(true);
};
function addSingleQuote(indexDifference: number) {
let newString: string = requestChoose.slice(0,indexDifference + 1) + "'" + requestChoose.slice(indexDifference + 1);
setRequestChoose(newString);
if(inputRef !== null && inputRef !== undefined) {
if (inputRef.current !== null && inputRef.current !== undefined) {
console.log("3 ");
if (inputRef.current.setSelectionRange !== undefined) {
inputRef.current.setSelectionRange(indexDifference, indexDifference);
}
}
}
}
function addDoubleQuote(indexDifference: number) {
let newString: string = requestChoose.slice(0,indexDifference + 1) + '"' + requestChoose.slice(indexDifference + 1);
setRequestChoose(newString);
}
useEffect(()=>{
if(testQuoteAddition === true) {
for(let i=0; i<requestChoose.length; i++) {
if(previousRequestChoose.charAt(i) !== requestChoose.charAt(i))
{
if (requestChoose.charAt(i) === "'") {
setEnterSingleQuote(true);
} else if (requestChoose.charAt(i) === '"') {
setEnterDoubleQuote(true);
}
}
}
}
setTestQuoteAddition(false);
},[testQuoteAddition])
useEffect(()=>{
if(enterSingleQuote === true){
let indexDifferenceInRequest: number = requestChoose.length + 1;
let findDifference: boolean = false
for(let i=0; i<requestChoose.length; i++) {
if(previousRequestChoose.charAt(i) !== requestChoose.charAt(i) && findDifference === false)
{
indexDifferenceInRequest = i;
findDifference = true;
}
}
addSingleQuote(indexDifferenceInRequest);
setEnterSingleQuote(false);
} else if (enterDoubleQuote === true){
let indexDifferenceInRequest: number = requestChoose.length + 1;
let findDifference: boolean = false
for(let i=0; i<requestChoose.length; i++) {
if(previousRequestChoose.charAt(i) !== requestChoose.charAt(i) && findDifference === false)
{
indexDifferenceInRequest = i;
findDifference = true;
}
}
addDoubleQuote(indexDifferenceInRequest);
setEnterDoubleQuote(false);
}
},[enterSingleQuote, enterDoubleQuote])
return(
<div>
<input ref={inputRef} type="text" onChange={inputHandler} value={inputChoose} className="text-center" placeholder="enter an input" />
</div>
);
}
export default QuoteInput;
This code allows me to add a new pair of quotes but the mouse cursor is then placed at the end of the string.
If I put: inputRef.current?.setSelectionRange(3, 3); in the inputHandler (the callback function of my input element), every time the user writes the cursor is reset to the third position of the string.
But if I put this same line: inputRef.current?.setSelectionRange(indexDifference, indexDifference); in the function which add a quote, as in the code above, nothing happens, but if I put console.log in the loop it displays well so my conditions are well met and the statement should execute.
I don't see what I'm doing wrong at all, if you could point me in the right direction it would help me a lot.
I just noticed another problem with my add quote function while I was writing this question.
When I type at normal speed in the search bar everything works normally. But if I suddenly decide to write super fast, the function of adding a quote when a user has put a first one stops working and doesn't work at all afterwards even if I start writing again at a normal speed.
Once the users knows how to used my tool well, it may happen that they type very fast and so this use case may occur.
When I display my state variables with console.log, it seems that before typing very fast, everything is triggered in the inputHandler. But after typing very fast, only state variables concerning the old string and the new string entered by the user are activated, the boolean state variable to launch the verification test of adding a new quote is no longer activated and therefore never goes to true (It should be set to true in inputHandler (the callback function of the input element in the return()) which then enables the first useEffect since the boolean state variable is in the dependency array.).
Does anyone know what can cause this behavior between an input, its callback function and the state variables?
If anyone understands my problem and is willing to help me it would be a great help.
I didn't find the answer to my second question (stopping the feature from working if the user spams the input with a lot of characters) but I finally found my problem. I was missing a useEffect for my previous code to work normally, because of that, one of my state variables didn't have time to update and this creates the strange behaviors of repositioning the cursor in the input.
In case someone is interested I put the correct code here (I added some validation conditions in some useEffects to avoid bugs of randomly adding quotation marks especially when copying and pasting):
import * as React from "react";
import { useEffect, useState, useRef } from "react";
const QuoteInput: React.FC = () => {
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputChoose, setInputChoose] = useState("");
const [previousInputChoose, setPreviousInputChoose] = useState("");
const [testQuoteAddition, setTestQuoteAddition] = useState(false);
const [enterSingleQuote, setEnterSingleQuote] = useState(false);
const [enterDoubleQuote, setEnterDoubleQuote] = useState(false);
const [writeNewSingleQuote, setWriteNewSingleQuote] = useState(false);
const [writeNewDoubleQuote, setWriteNewDoubleQuote] = useState(false);
const [repositionCaretBetweenQuotes, setRepositionCaretBetweenQuotes] = useState(false);
const [indexDifferenceInInput, setIndexDifferenceInInput] = useState(0);
const inputHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
setPreviousRequestChoose(requestChoose);
const enteredRequest = event.target.value;
setRequestChoose(enteredRequest);
setTestQuoteAddition(true);
};
useEffect(()=>{
if(testQuoteAddition === true && (previousInputChoose.length === inputChoose.length || previousInputChoose.length === inputChoose.length - 1)) {
for(let i=0; i<=inputChoose.length; i++) {
if(previousInputChoose.charAt(i) !== inputChoose.charAt(i))
{
let previousSingleQuoteNumber = previousRequestChoose.split("'").length - 1;
let previousDoubleQuoteNumber = previousRequestChoose.split('"').length - 1;
let previousQuoteNumber: number;
if (previousSingleQuoteNumber !== undefined && previousDoubleQuoteNumber !== undefined) {
previousQuoteNumber = previousSingleQuoteNumber + previousDoubleQuoteNumber;
} else if (previousSingleQuoteNumber !== undefined && previousDoubleQuoteNumber === undefined) {
previousQuoteNumber = previousSingleQuoteNumber;
} else if (previousSingleQuoteNumber === undefined && previousDoubleQuoteNumber !== undefined) {
previousQuoteNumber = previousDoubleQuoteNumber;
} else {
previousQuoteNumber = 0;
}
previousQuoteNumber = previousSingleQuoteNumber! + previousDoubleQuoteNumber!;
let currentSingleQuoteNumber = requestChoose.split("'").length - 1;
let currentDoubleQuoteNumber = requestChoose.split('"').length - 1;
let currentQuoteNumber: number;
if (currentSingleQuoteNumber !== undefined && currentDoubleQuoteNumber !== undefined) {
currentQuoteNumber = currentSingleQuoteNumber + currentDoubleQuoteNumber;
} else if (currentSingleQuoteNumber !== undefined && currentDoubleQuoteNumber === undefined) {
currentQuoteNumber = currentSingleQuoteNumber;
} else if (currentSingleQuoteNumber === undefined && currentDoubleQuoteNumber !== undefined) {
currentQuoteNumber = currentDoubleQuoteNumber;
} else {
currentQuoteNumber = 0;
}
currentQuoteNumber = currentSingleQuoteNumber! + currentDoubleQuoteNumber!;
if (inputChoose.charAt(i) === "'" && currentQuoteNumber !== previousQuoteNumber) {
setEnterSingleQuote(true);
} else if (inputChoose.charAt(i) === '"' && currentQuoteNumber !== previousQuoteNumber) {
setEnterDoubleQuote(true);
}
}
}
}
setTestQuoteAddition(false);
},[testQuoteAddition])
useEffect(()=>{
if(enterSingleQuote === true){
let findDifference: boolean = false
for(let i=0; i<inputChoose.length; i++) {
if(previousInputChoose.charAt(i) !== inputChoose.charAt(i) && findDifference === false)
{
setIndexDifferenceInInput(i);
findDifference = true;
}
}
setEnterSingleQuote(false);
setWriteNewSingleQuote(true);
} else if (enterDoubleQuote === true){
let findDifference: boolean = false
for(let i=0; i<inputChoose.length; i++) {
if(previousInputChoose.charAt(i) !== inputChoose.charAt(i) && findDifference === false)
{
setIndexDifferenceInInput(i);
findDifference = true;
}
}
setEnterDoubleQuote(false);
setWriteNewDoubleQuote(true);
}
},[enterSingleQuote, enterDoubleQuote])
useEffect(()=>{
if (writeNewSingleQuote === true) {
let newString: string = inputChoose.slice(0,indexDifferenceInInput + 1) + "'" + inputChoose.slice(indexDifferenceInInput + 1);
setInputChoose(newString);
if(inputRef !== null && inputRef !== undefined) {
if (inputRef.current !== null && inputRef.current !== undefined) {
if (inputRef.current.setSelectionRange !== undefined) {
setRepositionCaretBetweenQuotes(true);
}
}
}
} else if (writeNewDoubleQuote === true) {
let newString: string = inputChoose.slice(0,indexDifferenceInInput + 1) + '"' + inputChoose.slice(indexDifferenceInInput + 1);
setInputChoose(newString);
if(inputRef !== null && inputRef !== undefined) {
if (inputRef.current !== null && inputRef.current !== undefined) {
if (inputRef.current.setSelectionRange !== undefined) {
setRepositionCaretBetweenQuotes(true);
}
}
}
}
},[writeNewSingleQuote, writeNewDoubleQuote])
useEffect(()=>{
setWriteNewSingleQuote(false);
setWriteNewDoubleQuote(false);
if (repositionCaretBetweenQuotes === true) {
inputRef?.current?.setSelectionRange(indexDifferenceInInput + 1, indexDifferenceInInput + 1);
}
setRepositionCaretBetweenQuotes(false);
},[repositionCaretBetweenQuotes])
return(
<div>
<input ref={inputRef} type="text" onChange={inputHandler} value={inputChoose} className="text-center" placeholder="enter an input" />
</div>
);
}
export default QuoteInput;
const formSteps = [...multiStepForm.querySelectorAll("[data-step]")]
let currentStep = formSteps.findIndex(step => {
return step.classList.contains("active")
})
if (currentStep < 0) {
currentStep = 0
showCurrentStep()
}
multiStepForm.addEventListener("click", e => {
let incrementor
if (e.target.matches("[data-next]")) {
incrementor = 1
} else if (e.target.matches("[data-previous]")) {
incrementor = -1
}
if (incrementor == null) return
const inputs = [...formSteps[currentStep].querySelectorAll("input")]
const allValid = inputs.every(input => input.reportValidity())
if (allValid) {
currentStep += incrementor
showCurrentStep()
}
})
formSteps.forEach(step => {
step.addEventListener("animationend", e => {
formSteps[currentStep].classList.remove("hide")
e.target.classList.toggle("hide", !e.target.classList.contains("active"))
})
})
function showCurrentStep() {
formSteps.forEach((step, index) => {
step.classList.toggle("active", index === currentStep)
})
}
In this case of code, the validation of the data entry does not work and then the incriment for the other steps does not work. I also tried to add required in the inputs ignoring this code, the problem that making a "previous", the user does not have the possibility to go back blocked by the inserted required.
In the code, the incrementor object is already null, seen in log.console does not see it. I think the problem in this case is the querySelection as I noticed from the log.console that it takes data that is not of interest to me.
let check1 = 0;
const App: () => React$Node = () => {
const video = useRef();
const [check2, setCheck2] = useState(0);
let check3 = 0;
useEffect(
() => {
askPermission().then((record) => {
if (record) {
RNSoundLevel.start();
RNSoundLevel.onNewFrame = (data) => {
if(check1 < 10) check1++;
if(check2 < 10) setCheck(check2++);
if(check3 < 10) check3++;
}
}
});
If check1, the value can be read and written normally.
check2 and check3 give the value (0) at the time of the first render.
It seems that it can be solved by using check1, but the code does not seem to be clean.
I want to use check2, is there a way to read it?
I am currently trying to build an next.js app.
So what I want to do is passing a function to a component and call it with useEffect
I have the following component in order to change the props of the parents element. Therefore I pass the function from the parent like this:
<NumberInput name="height" update={manuelUpdate}></NumberInput>
The manuelUpdate function is a another props function from the actual parent:
const manuelUpdate = (name, value) => {
props.update(name, value);
};
It works fine if I run the props.function with the onclick functions. However as soon as I try to use it in useEffect, it returns that the function is not a function.
Maybe I am just thinking to complicated..
this is the component;
const NumberInput = ({ name, min = undefined, max = undefined, ...props }) => {
const minInput = min !== undefined
? min
: null;
const maxInput = max !== undefined
? max
: null;
const [numb, setNumb] = useState(0);
useEffect(() => {
console.log(props.update)
}, [numb]);
const increaseNumb = () => {
if (numb < maxInput || maxInput == null) {
setNumb(numb + 1)
}
props.update(name, numb)
};
const decreaseNumb = () => {
if (numb < minInput || minInput == null) {
setNumb(numb - 1)
}
};
const changeHandler = ({ e }) => {
let n = parseInt(e.target.value)
if (Number.isNaN(n)) {
setNumb(numb)
} else {
setNumb(n)
}
}
return (
<div className={styles.def_number_input, styles.number_input}>
<button onClick={decreaseNumb} className={styles.minus}></button>
<input className={styles.quantity} name="quantity" value={numb} onChange={(e) => changeHandler({ e })}
type="number" />
<button onClick={increaseNumb} className={styles.plus}></button>
</div>
);
};
sorry in advance if my question is stupid or my code messy, I am still in the process of learning :D
Issue was related to another problem. I was accidentally calling the component 2 times and forgot to adjust the parameters. CASE CLOSED