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]);
});
};
Related
UPDATE: OMG I'm using setTimeOut. But I still need an answer.
I made an application that accesses an array and outputs each of its elements after a certain period of time. When the array ends, execution stops.
There is a need to pause the execution. How can I do that?
const [isPaused, setIsPaused] = useState(false);
const togglePause = () => {
setIsPaused(!isPaused)
}
const soccData = data.player_positions; // cutting only IDs and positions from data
const [playerPosition, setPlayerPosition] = useState([]); // creating local state to work with IDs and positions
const getPlayerData = (arr) => { // function goes thru array of players and sets a new playerPosition on every step
for (let i = 0; i < arr.length; i++) {
setTimeout(() => {
setPlayerPosition(arr[i])
}, data.interval * (i + 1));
}
}
useEffect(() => {
getPlayerData(soccData)
return () => {
clearTimeout()
};
}, [soccData]);
return (
<div>{playerPosition}</div>
<p><button onClick={togglePause}></button></p>
)
I tried adding a condition if(!isPaused) to the function getPlayerData (and a dependency to useSeffect), but that didn't work.
Here's my code on codesandbox: https://codesandbox.io/s/youthful-paper-pvrq09
p.s. I found someone's code that allows to start/pause the execution: https://jsfiddle.net/thesyncoder/12q8r3ex/1/, but there's no ability to stop the execution when array ends.
The following works in your sandbox:
export default function App() {
const [isPaused, setIsPaused] = useState(false);
const [frame, setFrame] = useState(0);
const togglePause = () => {
setIsPaused(!isPaused);
};
const playerPosition = data.player_positions[frame];
useEffect(() => {
// do nothing if paused or the last frame is reached
if (isPaused || frame === data.player_positions.length - 1) {
return;
}
const to = setTimeout(() => {
setFrame((f) => f + 1);
}, data.interval);
return () => {
clearTimeout(to);
};
}, [frame, isPaused]);
return (
<div className="App">
<pre>{playerPosition}</pre>
<p>
<button onClick={togglePause}>{isPaused ? "play" : "pause"}</button>
</p>
</div>
);
}
As you can see, I removed the getPlayerData function. Instead, when the frame (which I called the index of the data we're currently at) or the isPaused variable changes, the effect is reran. It first (except for the very first execution) cleans up the still pending timeout (if there is one) and schedules a new one (if not paused and end is not reached) that increments the frame by one.
The main problem with your solution is the following:
const getPlayerData = (arr) => { // function goes thru array of players and sets a new playerPosition on every step
for (let i = 0; i < arr.length; i++) {
setTimeout(() => {
setPlayerPosition(arr[i])
}, data.interval * (i + 1));
}
}
This does not wait for the first timeout to occur, but instead schedules all frames in advance.
Therefore it is not pausable in a practical way (you could keep track of which timeout already ran, clear all of them when pausing and scheduling only the pending ones again once unpaused but that is not really practical)
setInterval() returns its id on execution. You can pass that id to clearInterval() to cancel it. Something like this might work:
let interval = 0;
interval = setInterval(() => {
// Your program logic
// ...
if(conditionTrueToCancelInterval) {
clearInterval(interval);
}
}, 1000)
import { ChangeEvent, useState } from 'react'
import { useInterval } from 'usehooks-ts'
export default function Component() {
// The counter
const [count, setCount] = useState<number>(0)
// Dynamic delay
const [delay, setDelay] = useState<number>(1000)
// ON/OFF
const [isPlaying, setPlaying] = useState<boolean>(false)
useInterval(
() => {
// Your custom logic here
setCount(count + 1)
},
// Delay in milliseconds or null to stop it
isPlaying ? delay : null,
)
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setDelay(Number(event.target.value))
}
return (
<>
<h1>{count}</h1>
<button onClick={() => setPlaying(!isPlaying)}>
{isPlaying ? 'pause' : 'play'}
</button>
<p>
<label htmlFor="delay">Delay: </label>
<input
type="number"
name="delay"
onChange={handleChange}
value={delay}
/>
</p>
</>
)
}
I want to create an array of non-overlapping random numbers with a length of 20.
However, when executed, the array is not complete and sometimes an error message "Too many re-renders" appears.
import React, {useState, useEffect} from "react";
const Ques = () => {
const [randArr, setRandArr] = useState([]);
const [randNum, setRandNum] = useState(0);
let rand = Math.floor(Math.random() * (19-0));
if(randArr.length !== 20 && randArr.includes(randNum) === true){
rand = Math.floor(Math.random() * (19-0));
setRandNum(rand)
}
useEffect(() =>{
setRandNum(rand);
setRandArr([...randArr, randNum]);
console.log("randNum : ", randNum);
console.log("randArr : ", randArr);
},[rand]);
return (
<div>
<button>Start</button>
</div>
);
};
export default Ques;
As has been pointed out in comments, you are declaring a new rand value each render cycle and using it as a dependency for an useEffect hook which then also enqueues a state update and triggers a rerender... repeat ad nauseam.
Instead of trying to iterate and populate an array with "random" numbers by "guessing" if they've already been selected, it'd be better to start with an array of [1..20] and "shuffle" it.
Example:
const res = Array.from({ length: 20 }, (_, i) => i + 1).sort(() => Math.random() - 0.5);
console.log(res.join(","));
You can just initialize your state to this value. No need for any loops and useEffect hooks.
const [randArr, setRandArr] = useState(
Array.from({ length: 20 }, (_, i) => i + 1).sort(() => Math.random() - 0.5)
);
import React, { useState, useEffect } from "react";
const App = () => {
const [random, setRandom] = useState([]);
const getRandom = () => Math.floor(Math.random() * (19 - 0));
useEffect(() => {
const arr = [];
for (let i = 0; i < 20; i++) {
arr.push(getRandom());
}
setRandom(arr);
console.log(arr);
}, []);
return (
<div>
<div>{random.join(",")}</div>
<button>Start</button>
</div>
);
};
export default App;
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.
I am trying to implement Conway's Game of Life in React, but it is freezing whenever a new generation is called. I assume this is because there is too much overhead caused by constantly re-rendering the DOM, but I don't know how to resolve this, nor can I think of an alternative to simply posting my entire code, so I apologise in advance for the verbosity.
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"
interface TileProps {
bool: boolean
}
const Tile: React.FC<TileProps> = ({bool}) => {
const colour = bool == true ? "#00FF7F" : "#D3D3D3"
return (
<div style = {{backgroundColor: colour}}/>
)
}
interface GridProps {
cells: boolean[][]
}
const StyledGrid = styled.div`
display: grid;
grid-template-columns: repeat(100, 1%);
height: 60vh;
width: 60vw;
margin: auto;
position: relative;
background-color: #E182A8;
`
const Grid: React.FC<GridProps> = ({cells}) => {
return (
<StyledGrid>
{cells.map(row => row.map(el => <Tile bool = {el}/>))}
</StyledGrid>
)
}
const randomBoolean = (): boolean => {
const states = [true, false];
return states[Math.floor(Math.random() * states.length)]
}
const constructCells = (rows: number, columns: number): boolean[][] => {
return constructEmptyMatrix(rows, columns).map(row => row.map(e => randomBoolean()))
}
const constructEmptyMatrix = (rows: number, columns: number): number[][] => {
return [...Array(rows)].fill(0).map(() => [...Array(columns)].fill(0));
}
const App: React.FC = () => {
const columns = 100;
const rows = 100;
const [cells, updateCells] = useState<boolean[][]>(constructCells(rows, columns));
useEffect(() => {
const interval = setInterval(() => {
newGeneration();
}, 1000);
return () => clearInterval(interval);
}, []);
const isRowInGrid = (i: number): boolean => 0 <= i && i <= rows - 1
const isColInGrid = (j : number): boolean => 0 <= j && j <= columns -1
const isCellInGrid = (i: number, j: number): boolean => {
return isRowInGrid(i) && isColInGrid(j)
}
const numberOfLiveCellNeighbours = (i: number, j: number): number => {
const neighbours = [
[i - 1, j], [i, j + 1], [i - 1, j + 1], [i - 1, j + 1],
[i + 1, j], [i, j - 1], [i + 1, j - 1], [i + 1, j + 1]
]
const neighboursInGrid = neighbours.filter(neighbour => isCellInGrid(neighbour[0], neighbour[1]))
const liveNeighbours = neighboursInGrid.filter(x => {
const i = x[0]
const j = x[1]
return cells[i][j] == true
})
return liveNeighbours.length;
}
const updateCellAtIndex = (i: number, j: number, bool: boolean) => {
updateCells(oldCells => {
oldCells = [...oldCells]
oldCells[i][j] = bool;
return oldCells;
})
}
const newGeneration = (): void => {
cells.map((row, i) => row.map((_, j) => {
const neighbours = numberOfLiveCellNeighbours(i, j);
if (cells[i][j] == true){
if (neighbours < 2){
updateCellAtIndex(i, j, false);
} else if (neighbours <= 3){
updateCellAtIndex(i, j, true);
}
else {
updateCellAtIndex(i, j, false);
}
} else {
if (neighbours === 3){
updateCellAtIndex(i, j, true);
}
}
}))
}
return (
<div>
<Grid cells = {cells}/>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
The application freezes because React does not batch your individual state updates. More information about this can be found in this answer
You have two options here.
Use ReactDOM.unstable_batchedUpdates:
This can be done with a single line change, but note that the method is not part of the public API
useEffect(() => {
const interval = setInterval(() => {
// wrap generation function into batched updates
ReactDOM.unstable_batchedUpdates(() => newGeneration())
}, 1000);
return () => clearInterval(interval);
}, []);
Update all states in one operation.
You could refactor your code to set updated cells only once. This option does not use any unstable methods
useEffect(() => {
const interval = setInterval(() => {
// `newGeneration` function needs to be refactored to remove all `updateCells` calls. It should update the input array and return the result
const newCells = newGeneration(oldCells);
// there will be only one call to React on each interval
updateCells(newCells);
}, 1000);
return () => clearInterval(interval);
}, []);
Does React.useMemo() have the cost to compare, like shouldComponentUpdate() in React.PureComponent ?
Is it bad to put variables changed often into useMemo() or useCallback()'s deps.
Thanks
Here is a ยต-benchmark, React.memo() seems to be the fastest, with PureComponent in the middle and useCallback() consistently the slowest:
class P extends React.PureComponent {
render() {
return <span onClick={() => this.props.n} />
}
}
function C(props) {
const c = React.useCallback(() => props.n, [props.n])
return <span onClick={c} />
}
const M = React.memo(props => <span onClick={() => props.n} />)
const target = document.getElementById('target')
function test(o) {
console.log('From fastest to slowest:')
const entries = Object.entries(o)
const results = new Map(entries.map(([name]) => [name, 0]))
for (let i = 0; i < 1e3; i++) {
for (let [name, X] of entries) {
const start = performance.now()
for (let j = 0; j < 1e2; j++) {
ReactDOM.render(<X n={i} />, target)
}
const took = performance.now() - start
results.set(name, results.get(name) + took)
}
}
const sorted = Array.from(results).sort(([, a], [, b]) => a-b)
for (let [name, t] of sorted) {
console.log(name, t)
}
}
test({PureComponent: P, useCallback: C, memo: M})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="target" hidden></div>