I'm trying to change the left style attribute of the element when a change happens but my code does not work, do you know why?
The Function Component:
const Link: React.FunctionComponent<{ name: string, href?: string }> = function ({ name, href }) {
const filteredName = name.toLowerCase().trim().split(" ").join("")
var res = 0
useEffect(()=>{
function handleResize() {
const w = globalThis.outerWidth
const h = globalThis.outerHeight
let index = 0
for (let elem = 0;elem<allDataElems.length;elem++) {
if (allDataElems[elem] === name) {
index = elem + 1
break
}
}
var elSize = null
try {
elSize = ulEl.current!.scrollTop + (ulEl.current!.firstElementChild!.scrollHeight * (index)) + index * 32 - 250
} catch {
elSize = 0
}
return (w*28*3/1000)*(elSize/h)
}
res = handleResize()
})
return <li style={{top: "20px",left: res + "px"}}><Anchor href={href || `/${name.replace(/ /g, "_")}`}><a onClick={() => closeNav()} onMouseOver={() => setHovering(filteredName as keyof typeof datas)}>{name}</a></Anchor></li>
}
Where I used it:
<Link name="Home" href="/"></Link>
<Link name="Project"></Link>
<Link name="Team"></Link>
<Link name="Description"></Link>
<Link name="Human Practices"></Link>
<Link name="Judging Form"></Link>
<Link name="Gallery"></Link>
You shouldn't be using useRef inside a different context than the function component. You can read all the rules in the official documentation.
In your case, you can solve it by creating a single ref with an Object and storing the element references as key/value pairs like so:
const Navbar = function () {
const linkRefs = useRef<{[key: string]: HTMLLIElement}>({});
useEffect(() => {
Object.values(linkRefs).forEach(element => {
// use element to access each element
});
});
return (
<div>
<Link ref={linkRefs.home} name="Home" href="/"></Link>
<Link ref={linkRefs.projects} name="Projects" href="/projects"></Link>
...
</div>
);
};
By the way I found the answer, just generated all the links in useEffect and it made my job significantly easier:
useEffect(() => {
const list = []
for (var i=0;i<datas.length;i++){
const t = datas[i].title
const res = handleResize(i + 1)
list.push(<li ref={resl[i + 1]} style={{top: "20px",left: res + "px"}}><Anchor href={/*datas[i].content?][1]*/ `/${t.replace(/ /g, "_")}`}><a onClick={() => closeNav()} onMouseOver={() => setHovering(i)}>{t}</a></Anchor></li>)
}
setLinks(list)
console.log(datas[hovering])
})
And then I returned it inside another html element.
I usually use useMemo instead of useEffect for this kind of matter.
const [change, setChange] = useState() //whatever your onclick value sets
const res = useMemo(() => {
const w = globalThis.outerWidth
const h = globalThis.outerHeight
let index = 0
for (let elem = 0;elem<allDataElems.length;elem++) {
if (allDataElems[elem] === name) {
index = elem + 1
break
}
}
var elSize = null
try {
elSize = ulEl.current!.scrollTop + (ulEl.current!.firstElementChild!.scrollHeight * (index)) + index * 32 - 250
} catch {
elSize = 0
}
return (w*28*3/1000)*(elSize/h)
}, [change]) //dependency. whatever that will retrigger res to get new value
you can use res like how u wrote in the question from hereon.
Related
how can write jest/enzyme test case for document.selector to get style value and set those value?
how to write UT for document selector
how to cover tertinary operator here
My button commponent:
<MyCustomButton
id="tooltip-btn"
iconName="icon-information"
className="btn"
onClick={tooltipHandler}
/>
handlerDetails here:
const tooltipHandler = () => {
const direction = document.querySelector('html').getAttribute('dir');
const InfoElement = document.querySelector('#info-tooltip.tooltip');
const tooltipContainerElement = document.querySelector('.tooltip__container--active');
const pageOffsetLeft = document.querySelector('.main-container').offsetLeft;
direction === 'ltr' ?( tooltipContainerElement.style.right = pageOffsetLeft + 'px') :
( tooltipContainerElement.style.left = pageOffsetLeft + 'px');
const currentLeftValue = InfoElement.style.left.replace('px', '').replace('-', '')
const fixedValue = direction === 'ltr' ? (pageOffsetLeft + 5) : -pageOffsetLeft-4;
const newLeftValue = (parseInt(currentLeftValue, 10) + fixedValue ) + 'px';
InfoElement.style.setProperty('--leftvalue', newLeftValue)// this is to set value for psuedo element;
}
I started with this here for UT:
it.only('tooltip postioning verification', () => {
const changehandler = jest.fn();
const wrapper = shallow(<MyMainComponenet {...testingProps} />)
.dive()
.dive();
jest.spyOn(document, 'querySelector').mockImplementation((selector) => {
switch (selector) {
case '.main-container':
return 82;
case 'html':
return dir;
}
});
expect(document.querySelector).toHaveBeenCalledTimes(2);
wrapper.find('#tooltip-btn').simulate('click')
expect(changehandler).toHaveBeenCalled(1);
})
``
codesandbox.io/s/github/Tmcerlean/battleship
I am developing a simple board game and need to increment a state variable when a player clicks on a cell with a valid move.
The functionality for validating the move and making the move is all in place, however, I am having difficulty updating the state within the event listener.
I can see that the state is being updated when observed from a useEffect hook, but not when viewed from within the function (even following successive calls).
I have done some reading and believe it could have something to do with having a stale closure, but I am not certain.
My approach to solve this issue was to remove and then re-add the click event listener following every click by the user.
My assumption was that this would cause the correct (newly incremented) state variable to be picked up. Unfortunately, this does not appear to be the case and within the event listener function, the variable is never incremented from 0.
I initialise the state variable here:
const [placedShips, setPlacedShips] = useState(0);
Next, a click event listener is applied to each cell within the gameboard:
const clickListener = (e) => {
e.stopImmediatePropagation();
let direction = currentShip().direction;
let start = parseInt(e.target.id);
let end = start + currentShip().length - 1;
if (playerGameboard.checkValidCoordinates(direction, start, end)) {
playerGameboard.placeShip(placedShips, direction, start, end);
setPlacedShips((oldValue) => oldValue + 1);
console.log(placedShips);
}
};
const setEventListeners = () => {
const gameboardArray = Array.from(document.querySelectorAll(".cell"));
gameboardArray.forEach((cell) => {
cell.addEventListener("click", (e) => {
clickListener(e);
});
});
};
You will see that the setPlacedships state variable is incremented here and there is a console log to report its value.
I am aware that the useState hook is asynchronous and so console.log will show 0 for the first time it is called. Consequently, I have a useEffect hook deployed outside of the function which also contains a console.log to report the changed value of setPlacedShips:
useEffect(() => {
removeEventListeners();
setEventListeners();
console.log(placedShips)
}, [placedShips])
After every click the placedShips variable is incremented by 1 and then two functions are run:
const removeEventListeners = () => {
const gameboardArray = Array.from(document.querySelectorAll(".cell"));
gameboardArray.forEach((cell) => {
cell.removeEventListener("click", (e) => {
clickListener(e);
});
});
};
which is immediately followed by the original setEventListeners function:
const setEventListeners = () => {
const gameboardArray = Array.from(document.querySelectorAll(".cell"));
gameboardArray.forEach((cell) => {
cell.addEventListener("click", (e) => {
clickListener(e);
});
});
};
As mentioned above, the issue is that the console log within the setEventListeners function constantly remains at 0, while the console log within the useEffect hook increments as expected.
For reference, here is the full component I am working on currently:
import React, { useEffect, useState, useLayoutEffect } from "react";
import gameboardFactory from "../../factories/gameboardFactory";
import Table from "../Reusable/Table";
import "./GameboardSetup.css";
// -----------------------------------------------
//
// Desc: Gameboard setup phase of game
//
// -----------------------------------------------
let playerGameboard = gameboardFactory();
const GameboardSetup = () => {
const [humanSetupGrid, setHumanSetupGrid] = useState([]);
const [ships, _setShips] = useState([
{
name: "carrier",
length: 5,
direction: "horizontal",
},
{
name: "battleship",
length: 4,
direction: "horizontal",
},
{
name: "cruiser",
length: 3,
direction: "horizontal",
},
{
name: "submarine",
length: 3,
direction: "horizontal",
},
{
name: "destroyer",
length: 2,
direction: "horizontal",
},
]);
const [placedShips, setPlacedShips] = useState(0);
const createGrid = () => {
const cells = [];
for (let i = 0; i < 100; i++) {
cells.push(0);
}
};
const createUiGrid = () => {
const cells = [];
for (let i = 0; i < 100; i++) {
cells.push(i);
}
let counter = -1;
const result = cells.map((cell) => {
counter++;
return <div className="cell" id={counter} />;
});
setHumanSetupGrid(result);
};
const setUpPlayerGrid = () => {
// createGrid('grid');
createUiGrid();
};
const currentShip = () => {
return ships[placedShips];
};
const clickListener = (e) => {
e.stopImmediatePropagation();
let direction = currentShip().direction;
let start = parseInt(e.target.id);
let end = start + currentShip().length - 1;
if (playerGameboard.checkValidCoordinates(direction, start, end)) {
playerGameboard.placeShip(placedShips, direction, start, end);
setPlacedShips((oldValue) => oldValue + 1);
console.log(placedShips);
}
};
const setEventListeners = () => {
const gameboardArray = Array.from(document.querySelectorAll(".cell"));
gameboardArray.forEach((cell) => {
cell.addEventListener("click", (e) => {
clickListener(e);
});
cell.addEventListener("mouseover", (e) => {
e.stopImmediatePropagation();
let direction = currentShip().direction;
let start = parseInt(cell.id);
let end = start + currentShip().length - 1;
if (currentShip().direction === "horizontal") {
const newShip = [];
if (playerGameboard.checkValidCoordinates(direction, start, end)) {
for (let i = start; i <= end; i++) {
newShip.push(i);
}
newShip.forEach((cell) => {
gameboardArray[cell].classList.add("test");
});
}
} else {
const newShip = [];
if (playerGameboard.checkValidCoordinates(direction, start, end)) {
for (let i = start; i <= end; i += 10) {
newShip.push(i);
}
newShip.forEach((cell) => {
gameboardArray[cell].classList.add("test");
});
}
}
});
cell.addEventListener("mouseleave", (e) => {
e.stopImmediatePropagation();
let direction = currentShip().direction;
let start = parseInt(cell.id);
let end = start + currentShip().length - 1;
if (currentShip().direction === "horizontal") {
const newShip = [];
if (playerGameboard.checkValidCoordinates(direction, start, end)) {
for (let i = start; i <= end; i++) {
newShip.push(i);
}
newShip.forEach((cell) => {
gameboardArray[cell].classList.remove("test");
});
}
} else {
const newShip = [];
if (playerGameboard.checkValidCoordinates(direction, start, end)) {
for (let i = start; i <= end; i += 10) {
newShip.push(i);
}
newShip.forEach((cell) => {
gameboardArray[cell].classList.remove("test");
});
}
}
});
});
};
const removeEventListeners = () => {
const gameboardArray = Array.from(document.querySelectorAll(".cell"));
gameboardArray.forEach((cell) => {
cell.removeEventListener("click", (e) => {
clickListener(e);
});
});
};
useEffect(() => {
setUpPlayerGrid();
// setUpComputerGrid();
}, []);
useEffect(() => {
console.log(humanSetupGrid);
}, [humanSetupGrid]);
// Re-render the component to enable event listeners to be added to generated grid
useLayoutEffect(() => {
setEventListeners();
});
useEffect(() => {
removeEventListeners();
setEventListeners();
console.log(placedShips);
}, [placedShips]);
return (
<div className="setup-container">
<div className="setup-information">
<p className="setup-information__p">Add your ships!</p>
<button
className="setup-information__btn"
onClick={() => console.log(placedShips)}
>
Rotate
</button>
</div>
<div className="setup-grid">
<Table grid={humanSetupGrid} />
</div>
</div>
);
};
export default GameboardSetup;
I am quite confused what is happening here and have been stuck on this problem for a couple of days now - if anybody has any suggestions then they would be highly appreciated!
Thank you.
const removeEventListeners = () => {
const gameboardArray = Array.from(document.querySelectorAll(".cell"));
gameboardArray.forEach((cell) => {
cell.removeEventListener("click", (e) => {
clickListener(e);
});
});
};
The above code does not remove any event listeners, which is probably the reason why 0 is still being logged. You pass a new anonymous function to removeEventListener. Since the function is just created it will never remove any event listeners, because it is not registered as an event listener.
Two different functions that do the same are not equal, which is why the event listener is not removed.
const a = (e) => clickListener(e); // passed to addEventListener
const b = (e) => clickListener(e); // passed to removeEventListener
console.log(a == b); //=> false
To add and remove events you cannot use anonymous functions. You either have to use named functions, or store the function in a variable. Then register and remove the event listener using the function name or variable.
Since you only forward the event to the clickListener you can simply replace your event handler registration with:
cell.addEventListener("click", clickListener);
Then remove it using:
cell.removeEventListener("click", clickListener);
Note that this scenario could've been avoided if you passed your event handlers using a more React approach. Instead of using cell.addEventHandler(...) you could've passed the event on creation of this element. eg. <div className='cell' id={counter} onClick={clickListener} />
When working with React you should try to not manipulate the DOM manually. React Components have Synthetic Events, which means that you don't need to add event listeners the vanilla way.
Just add each synthetic event to the cell component with its corresponding handler.
You can do it in the createUiGrid function:
const createUiGrid = () => {
const cells = [];
for (let i = 0; i < 100; i++) {
cells.push(i);
}
let counter = -1;
const result = cells.map((cell) => {
counter++;
return <div className="cell" id={counter} onClick={onClickHandler} onMouseOut={onMouseOutHandler} onMouseOver={onMouseOverHandler} />;
});
setHumanSetupGrid(result);
};
And then just move the code you did on vanilla to each corresponding handler (be sure to remove all listener manipulation before testing).
I have a naive pattern matching function, and I'm trying to slow down execution of each comparison so I can create a visualiser for it. However, I want to be able to access my i and j variables outside of the function. I am attempting to do this by declaring them outside of the function, passing them in, and returning them after each match. This way I can press a button to control the flow of execution. However, they are not being returned properly, and I suspect this has something to do with my use of async/await, and the need to return the values as a Promise.
https://codesandbox.io/s/staging-http-0zm04?file=/src/App.tsx:0-1072
import React, { useState } from "react";
import "./styles.css";
const delay = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
export const naive = async (text: string, pattern: string, i: number, j: number) => {
const matches = [];
let n = text.length;
let m = pattern.length;
while (i < n){
while (j < pattern.length && pattern[j] === text[i + j]){
j += 1;
await delay(500);
}
if (j === m){
matches.push(i)
}
return [i, j, matches]
}
}
export default function App() {
const [text, setText] = useState<string>("abcdefghijklmnopqrstuvwxyzabcd")
const [pat, setPat] = useState<string>("abc")
const [i, updateI] = useState(0);
const [j, updateJ] = useState(0);
const nextMatch = () => {
let results = naive(text, pat, i, j);
updateI(results[0]);
updateJ(results[1]);
}
return (
<div>
<button style = {{width: "100px", height: "50px"}}onClick = {() => nextMatch()}/>
{i}
{j}
</div>
);
}
As navie is an async function you have to add then.This would help to return correct i and j values
const nextMatch = () => {
naive(text, pat, i, j).then((results) => {
updateI(results[0]);
updateJ(results[1]);
});
};
Currently building hanging man game and in my case there is an array which contains 6 items. So in one round every item will be shown once of course, and also keep in mind that it is not possible to show the duplicated items in the same round. Below I was attempting to write the code that do the job, unfortunately this is not working in my case. The problem with my code is that it will keep looping through the array and when it comes to the latest index it should shuffle the array. But I get the error word is undefined if the
function shuffle
is being called
let data = ['apple', 'boring', 'citrus', 'dopamine', 'earth'];
const randomWord = () => {
setArrayCount(arrayCount + 1);
console.log(arrayCount, data.length);
if(arrayCount > data.length) {
setArrayCount(0);
shuffle(data);
} else {
}
replaceLetter(data[arrayCount].word);
cleanWord();
}
const shuffle = (a) => {
// create copy or new array
let newArr = [].concat(a);
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[j]] = [newArr[j], newArr[i]];
}
console.log(newArr);
return newArr;
}
#frontend. Please see my code:
const App = () => {
let data = ["apple", "boring", "citrus", "dopamine", "earth"];
const [wordArray, setWordArray] = useState([]);
const clearData = () => {
setWordArray([]);
return ["apple", "boring", "citrus", "dopamine", "earth"]
}
const randomElem = data => {
while (data.length > 0) {
const indexChoose = Math.floor(Math.random() * data.length);
wordArray.push(data[indexChoose]);
setWordArray(wordArray);
data.splice(indexChoose, 1);
}
data = clearData()
};
return (
<div className="App">
<button
onClick={() => {
randomElem(data);
console.log('result: ',wordArray);
}}
>
Change
</button>
<button
onClick={() => {
clearData();
console.log('result: ',wordArray);
}}
>
Clear
</button>
</div>
);
};
I am using this to create a 3D interactive view for a product: https://github.com/aldrinc/React360. The code in question is:
import React, { Component } from "react";
import "./React360.css";
// You can play with this to adjust the sensitivity
// higher values make mouse less sensitive
const pixelsPerDegree = 3;
class React360 extends Component {
static defaultProps = { dir: 'awair-360', numImages: 55 };
state = {
dragging: false,
imageIndex: 0,
dragStartIndex: 0
};
componentDidMount = () => {
document.addEventListener("mousemove", this.handleMouseMove, false);
document.addEventListener("mouseup", this.handleMouseUp, false);
};
componentWillUnmount = () => {
document.removeEventListener("mousemove", this.handleMouseMove, false);
document.removeEventListener("mouseup", this.handleMouseUp, false);
};
handleMouseDown = e => {
e.persist();
this.setState(state => ({
dragging: true,
dragStart: e.screenX,
dragStartIndex: state.imageIndex
}));
};
handleMouseUp = () => {
this.setState({ dragging: false });
};
updateImageIndex = currentPosition => {
let numImages = this.props.numImages;
const pixelsPerImage = pixelsPerDegree * (360 / numImages);
const { dragStart, imageIndex, dragStartIndex } = this.state;
// pixels moved
let dx = (currentPosition - dragStart) / pixelsPerImage;
let index = Math.floor(dx) % numImages;
if (index < 0) {
index = numImages + index - 1;
}
index = (index + dragStartIndex) % numImages;
// console.log(index, dragStartIndex, numImages)
if (index !== imageIndex) {
this.setState({ imageIndex: index });
}
};
handleMouseMove = e => {
if (this.state.dragging) {
this.updateImageIndex(e.screenX);
}
};
preventDragHandler = e => {
e.preventDefault();
};
renderImage = () => {
const { imageIndex } = this.state;
if (isNaN(imageIndex)) {
this.setState({imageIndex: 0})
return
}
return (
<div className="react360">
<img
className="react-360-img"
alt=""
src={require(`./${this.props.dir}/${imageIndex}.jpg`)}
/>
</div>
);
};
render = () => {
return (
<div
className="react-360-img"
onMouseDown={this.handleMouseDown}
onDragStart={this.preventDragHandler}
>
{this.renderImage()}
</div>
);
};
}
export default React360;
I am running into an issue where hardcoding the variable for the number of images (numImages) results in proper function but when I set the number of images as a prop let numImages = this.props.numImages; my image index sometimes goes to NaN. I've made a hacky workaround by setting the imageIndex state to 0 if it is NaN but I would like to resolve this issue correctly by understanding what exactly is different with setting a variable using props vs hardcoding.