Why my createdElement is doubling in reactjs useEffect()? - javascript

So my issue here is quite simple but you don't have to understand the others codes just only the useEffect() parts..
My custom mousecursor text is doubling when I tried to hover the text
here is the lines of codes.
const cursorIntro = document.querySelector(".cursor");
const options = document.querySelector(".introduction .nav-options");
options.addEventListener("mousemove", function s(e) {
var rect = options.getBoundingClientRect();
var x = e.clientX - rect.left; //x position within the element.
var y = e.clientY - rect.top;
cursorIntro.style.left = x + "px";
cursorIntro.style.top = y + "px";
});
function OnSelect() {
const optionsSelection = document.querySelectorAll(".options");
optionsSelection.forEach((elem, i) => {
// console.log(elem.children[1].children[0].children[0])
elem.children[1].children[0].children[0].addEventListener(
"mouseleave",
() => {
cursorIntro.removeChild(cursorIntro.lastChild);
// cursorIntro.innerHTML = ""
}
);
elem.children[1].children[0].children[0].addEventListener(
"mouseenter",
() => {
// elem.children[1].children[0].children[0].classList.add('')
const createElement = document.createElement("h4");
createElement.innerText =
elem.children[1].children[0].children[0].dataset.name;
cursorIntro.appendChild(createElement);
}
);
});
}
OnSelect();
As you see I have a custom mousecursor on it and because that is where I want to append the text when it hover the text elements.
This is inside the useEffect() when I'm calling it...but one that I wasn't sure is that I only call back once the addEventListener per each.
The reason I used createElement because if I used innerHTML without using a createElement I can't add another some items because my plan here is to added something more in mousecursor
THIS IS THE CODEPEN

go to index.js and replace StrictMode to React.Fragment, in dev mode react re-renders twice

Related

Dynamically add html elements in react

Please how can I dynamically add html element (like span) to elements in a react component. I want to implement something like this
let text1 = '"Text one sentence.';
let text2 = 'Text two sentence';
const textElement1 = document.querySelector(".text-1");
const textElement2 = document.querySelector(".text-2");
let textSpan;
function setText(t) {
t == text1 ? (textElement1.innerHTML = "") : (textElement2.innerHTML = "");
t.split("").map((x) => {
let charElement = document.createElement("span");
let charNode = document.createTextNode(x);
charElement.appendChild(charNode);
t == text1
? textElement1.appendChild(charElement)
: textElement2.appendChild(charElement);
});
textSpan = document.querySelectorAll("span");
}
function setFontWeight() {
textSpan.forEach((element) => {
let position = element.getBoundingClientRect();
// Calculate The Distance Between Cursor And Target Elements
let distance = Math.ceil(
Math.sqrt((position.x - pointerX) ** 2 + (position.y - pointerY) ** 2)
);
// The Longer The Distance The Lower The Font Weight
element.setAttribute(
"style",
`font-variation-settings: 'wght' ${900 - distance * 2};`
);
});
}
This is the react component I have created
function TopSidePane(){
return(
<div onMouseMove={(e)=>Hover(e)} className="top-side-pane">
<div className="text-container">
<h1 className="text-1">{text1}</h1>
<h1 className="text-2"> {text2}</h1>
</div>
<div className="custom-pointer" ></div>
</div>
);
}
Anytime there is a mouse movement on the component (if it get to the text inside the component) I want to be able to add span elements to the elements with the class names 'text-1' and 'text-2'. After I add the elements, I call the setFontWeight to make the weight of the hovered letters bold.
Please any help on how to implement this in react. I am new to react.

Copy scrollTop value from textarea to div

I have a div and a textarea inside a parent div. I am trying to copy the scrollTop value of the textarea to the div so it moves in sync with the textarea scrolling.
The problem seems to be when i add text into the textarea and then press enter for a new line, the div scrollTop value doesn't seem to update but the textarea scrollTop value does.
If i press enter again both values update but it seems the div scrollTop value is one step behind the textarea
https://codesandbox.io/s/objective-feather-ngq8t
handleScroll = (e) => {
setTextareaScrollTop(e.target.scrollTop);
e.target.previousElementSibling.scrollTop = e.target.scrollTop;
setDivScrollTop(e.target.previousElementSibling.scrollTop);
};
One simple workaround is to remove the setDivScrollTop from the handleScroll and add a new line \n after setting the red div's text. Note that this character acts like a caret and allows it to follow the other div.
handleScroll = (e) => {
setTextareaScrollTop(e.target.scrollTop);
e.target.previousElementSibling.scrollTop = e.target.scrollTop;
// setDivScrollTop(e.target.scrollTop);
};
handleInput = (e) => {
console.log(divScrollTop, textareaScrollTop)
setText(e.target.value + "\n"); // add "\n"
};
As seen here, Codesandbox
Also I've added border style to the text area element and spellCheck={false} to make it possible to see they're equal.
I made some mod to your code, https://codesandbox.io/s/empty-voice-7w3ze
const useUpdate = () => {
const [, dispatch] = useState(0);
const ref = useRef(() => {
dispatch((v) => v + 1);
});
return ref.current;
};
And when you need to repaint, just do
handleScroll = (e) => {
e.target.previousElementSibling.scrollTop = e.target.scrollTop;
refresh();
};
I didn't answer your question exactly according to what you want, but i noticed, there's no role the setState plays, so i removed both of them and replaced with a useUpdate. Let me know what you think on this approach.
If i remove both setState you had earlier, i do see the issue you described.

Is there a way to add "active" class to list item based on its location in a div?

I've got a fixed height div with a list of clickable list items. In the middle of the div, I have an absolute positioned line that is meant to signify a selected item. Right now, it's just a static line.
Is there a way to add an active class to the list item as it is "selected" by the line?
http://dev.chrislamdesign.com/shortwave/
One of the solutions is to use document.elementFromPoint(x, y) method. Something like this:
let lineCoords, lineTop, lineCenter;
// try to remove these two lines, leave just the scroll event listener
// document.getElementById('scrollUp1').addEventListener('click', setActive);
// document.getElementById('scrollDown1').addEventListener('click', setActive);
// 2nd edition: added these event listeners
window.addEventListener('scroll', getLineCoords);
window.addEventListener('load', getLineCoords);
// added this line
document.getElementById('wrap-scroll-1').addEventListener('scroll', setActive);
function setActive() {
const li = document.elementFromPoint(lineCenter, lineTop + lineCoords.height);
clearActive();
li.classList.add('active');
}
function clearActive() {
const ul = document.getElementById('ul-scroll-1');
const activeLi = ul.querySelector('li.active');
if (activeLi) {
activeLi.classList.remove('active');
}
}
// 2nd edition: added this function
function getLineCoords() {
lineCoords = document.querySelector('.orange-line').getBoundingClientRect();
lineTop = lineCoords.top;
lineCenter = lineCoords.left + (lineCoords.width / 2);
}
You can see this in action here: JsBin. These up and down buttons are assumed to scroll the list, but I don't have this functionality, because that's not a point here - just scroll it youself. The point here is that the element under the orange line will get active class each time you click one of these buttons.
So, take this code and edit it as you want.
Edited: I added an scroll event listener to the #wrap-scroll-1 container, because I guess the scroll event occurs right on it. If not - you can change it. Look at this in action: JsBin
2nd edition: Added event listeners to reassign the orange line coordinates every time when the page scrolled, and also when the page is loaded. Take a look at the result here: JsBin
You could compare the rects of the line and each option to find which is selected:
const line = document.querySelector('#emotional .orange-line');
const options = document.querySelector('#emotional .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
The selected option is the one with the largest y value without exceeding the y value of the orange line. To make things simple, the loop just returns the first option with a y value greater than the line, then grabs its previous sibling.
Update
To get this code to run whenever the user scrolls, you can wrap it in a function and attach it as an eventListener:
function updateSelection(menuId) {
const line = document.querySelector(menuId + ' .orange-line');
const options = document.querySelector(menuId + ' .selection-options').children;
const lineY = line.getBoundingClientRect().y;
const optionAfterSelected = [...options].find((option) => {
return option.getBoundingClientRect().y > lineY;
});
const selected = optionAfterSelected.previousSibling;
selected.classList.add('selected');
}
document.querySelector('#emotional .wrap-container').addEventListener('wheel', () => {
updateSelection('#emotional');
});
document.querySelector('#genre .wrap-container').addEventListener('wheel', () => {
updateSelection('#genre');
});
document.querySelector('#cinematic .wrap-container').addEventListener('wheel', () => {
updateSelection('#cinematic');
});

How to test the value of a label

I have a list of news and I can modify the news. When I modify one she stay at her inital position (if she was at place 5 she stay here). But when I click "modify this new" a form pop to the bottom of the page and then when I submit the modifications I want to scroll to this modified new. To do that I would use something like
find position where label.text() == titleModified
then I could do
window.scrollTo(0,result of the line above);
For the moment I tried to do document.getElementById but it always bring me to the top of the page...
Thank you for helping me
PS: there is a link on Plunker to see the structure of news : https://plnkr.co/edit/mLCxPYaBR56KkEOLNF8F?p=preview
and this is my JS for the modification:
'submit .modifyArticle'(event) {
event.preventDefault();
const target = event.target;
const textModif = target.textModif.value;
const titreModif = target.titreModif.value;
const photoModif = target.photoModif.value;
const idModif = Session.get('idTemp');
//test if values from the from are not empty or whitespaced
if ((/\S/.test(textModif))||(/\S/.test(titreModif))) {
console.log("2ème étape: dans body.js -> submit .modifyArticle");
Meteor.call('articles.modify',idModif,textModif,titreModif,photoModif);
Session.set('wantModif',false);
//here my new is modified so I want to scroll to her
//var titreModified actually contains the title after the modification but only for the first new...
setTimeout(function(){
var titreModified = document.getElementById("titreArticle");
var position = titreModified.offsetTop;
console.log("Y: " + position);
console.log("var titreArticleModif: "+ titreModified.textContent);
}, 50)
[...]
EDIT (i'll put the solution here but the real hero is #alexr101): First I had to add a class to my label <label class="titreArticle">{{titre}}</label>
then this is the JS:
`setTimeout(function(){
$('.titreArticle').each(function(i, obj) {
if(obj.textContent.includes(titreModif)){
alert("le titre devrait être: " + obj.textContent);
var position = obj.offsetTop;
window.scrollTo(0,position);
return false;
}
});
}, 20)`
the timeout is here because it's not 100% real-time and I had to wait until the new title was put in the DOM.
Try getting the y position of the element like so:
//get modified element
var titleModified = document.getElementById("modifiedElement");
//get y position of element through offsetTop function
var yPosition = titleModified.offsetTop;
//Set scroll amount
window.scrollTo(0, yPosition );
Would that work?

Create a child div accordingly if another child already exists at that location (x,y)

I am inserting a textarea to a side bar (exactly on the right to), wherever a click is made on the page. The code is:
$('#page_to_be_clicked').click(function(e){
var offset = $(this).offset();
var comment_box_y_coord = e.pageY - offset.top;
alert(comment_box_y_coord);
$("#sidebar").append('<textarea id="cmmnt" rows="4" cols="10" '+
'style="position:absolute;top:'+comment_box_y_coord +
'px;left:5px"></textarea>');
})
The problem with this is that, if a textarea is already present at the location, it will overlap the existing, i.e. if a click is made twice at the same point on the page, then two textareas are created on top of each other. Instead, it should be created one below the other.
Is there a way to check, if a child already exists at the required co-ordinates?
Any help will be appreciated. Thanks.
How exactly should the textareas appear on clicks in a sequence:
This needs to be tested properly, but I think you need to do this:
DEMO
In your function change this line:
var comment_box_y_coord = checkCoords(e.pageY - offset.top);
and then add this function:
function checkCoords(y) {
if ($("textarea").length>0) {
$ts = $("textarea");
for (var i = 0; i<$ts.length;i++) {
var $ti = $ts.eq(i),
tcoords = [$ti.offset().top, $ti.offset().top+$ti.height()]
if (y>=tcoords[0] && y <= tcoords[1]) {
y = tcoords[1]+3;
}
}
}
return y;
}

Categories