My goal is to loop through an array of characters and end on each letter of a given word. My code is currently displaying all of these elements at once, but I want them to display sequentially. Here's what I currently have:
Current view
I'd like to return the array that ends with h(wait a few moments), array that ends with e (wait a few moments), and so on. I can't figure out to to attach the arrayIndex to the nested map though.
DisplayName.js
import React, { useState, useEffect } from "react";
const DisplayName = ({ characters, first }) => {
const [charIndex, setCharIndex] = useState(0);
const [arrayIndex, setArrayIndex] = useState(0);
let arrayContainer = [];
first.map((letter, i) => {
arrayContainer.push([]);
arrayContainer[i].push(characters.concat(first[i]));
return arrayContainer;
});
// I can't figure out how to attach arrayIndex here. I am
// also not using j currently, but kept it for now in case I need
// a key for the return statements.
const fullList = arrayContainer.map((letterArr, j) => {
return letterArr.map(char => {
return (char[charIndex])
})
});
useEffect(() => {
let timer;
let secondTimer;
if (charIndex < characters.length) {
timer = setTimeout(() => {
setCharIndex(charIndex + 1)
}, 75)
}
if (arrayIndex < first.length - 1) {
secondTimer = setTimeout(() => {
setArrayIndex(arrayIndex + 1)
}, 75)
}
return () => {
clearTimeout(timer);
clearTimeout(secondTimer);
};
}, [charIndex, characters, arrayIndex, first]);
return (
<div>{fullList}</div>
)
};
export default DisplayName;
App.js
import React from 'react';
import DisplayName from './DisplayName';
import './App.css';
function App() {
const first = 'hello'.split('');
const funChars = [
'⏀', '⎷', '⌮', '⋙', '⊠', '⎳', '⍼',
'⍣', '╈', '╳', '☀', '★', '☍', 'ↂ','▅'];
return (
<div className="glow" style={{ minHeight: '100vh'}}>
<span style={{ letterSpacing: 12}}><DisplayName first={first} characters={funChars}/></span>
</div>
);
}
export default App;
I've also tried something like const [rendered, setRendered] = useState(false); without success, which I tried attaching to the j key.
If I understand your question, you want to iterate over the first string up to an index and display a "rolling" fun character while iterating the string.
Intuitively I think it is easier to think of of slicing the front of the first string to an index, and appending the fun character.
iteration
index
text.substring(0, index)
result(s)
0
0
""
'⏀', '⎷', '⌮',...
1
1
"h"
'h⏀', 'h⎷', 'h⌮',...
2
2
"he"
'he⏀', 'he⎷', 'he⌮',...
3
3
"hel"
'hel⏀', 'hel⎷', 'hel⌮',...
4
4
"hell"
'hell⏀', 'hell⎷', 'hell⌮',...
5
5
"hello"
'hello'
The tricky issue is using two separate timers/intervals to increment the index for the first string and to increment an index into the fun characters array. Here is a solution I came up with.
Use a React ref to hold a interval timer reference for the rolling fun characters.
Single useEffect hook to start the "rolling" fun character index incrementing on an interval. Start a timeout on incrementing over the first string char array, if there is still length to iterate, enqueue another timeout, otherwise run clean up functions to clear timers and state.
Slice the first string up to index arrayIndex and conditionally append a "rolling" fun character.
Code:
const DisplayName = ({ characters, first }) => {
const charTimerRef = useRef(null);
const [charIndex, setCharIndex] = useState(null);
const [arrayIndex, setArrayIndex] = useState(0);
useEffect(() => {
let timerId;
const cleanupTimerRef = () => {
setCharIndex(null);
clearInterval(charTimerRef.current);
charTimerRef.current = null;
};
if (!charTimerRef.current) {
setCharIndex(0);
charTimerRef.current = setInterval(() => {
setCharIndex((i) => i + 1);
}, 75);
}
if (arrayIndex < first.length) {
timerId = setTimeout(() => {
setArrayIndex((i) => i + 1);
}, 1000);
} else {
cleanupTimerRef();
}
return () => {
clearTimeout(timerId);
cleanupTimerRef();
};
}, [arrayIndex, first]);
const fullList =
first.substring(0, arrayIndex) +
(charIndex ? characters[charIndex % characters.length] : "");
return <div>{fullList}</div>;
};
Demo
Related
Introduction
I have select box for user to choose how much data he want to see in visualisation , this data is array of possitive unique numbers randomly choosen. I need to change this data length to put it into d3.js to drawVisualistion there
Problem
When i select some option from select box my application start laggy that i need to close the tab. This is the code i used.
import generateData from "./generateData";
const dataSizeSelect = document.getElementById(
"data-size"
) as HTMLSelectElement;
let data:number[] = [];
dataSizeSelect.addEventListener("change", () => {
const dataSize = parseInt(dataSizeSelect.value, 10);
data = generateData(dataSize)
});
console.log(data);
export {};
Then i tried in diffrent method but only gives zero , after some reading about how it is done in addEventListener function i read that it always be zero becouse code i have is asonchronus:
import generateData from "./generateData";
const dataSizeSelect = document.getElementById(
"data-size"
) as HTMLSelectElement;
let dataSize: number = 0;
dataSizeSelect.addEventListener("change", () => {
dataSize = parseInt(dataSizeSelect.value, 10);
});
const data = generateData(dataSize);
export {};
And this is the generateData func:
const generateData = (numPoints: number): number[] => {
const data = new Set();
while (data.size < numPoints) {
const randomNumber = Math.floor(Math.random() * 100);
if (randomNumber > 0) {
data.add(randomNumber);
}
}
return [...data] as number[];
};
export default generateData;
I am trying to create a simple counter component in React. It should start at 0, then iterate up to the passed in number. Here is the code I currently have:
const Counter = ({ number }) => {
const [currentNumber, setCurrentNumber] = useState(0);
for (let i = 0; i < number; i++) {
setTimeout(() => setCurrentNumber(currentNumber + 1), 2000);
}
return <span>{currentNumber}</span>;
};
export default Counter;
What happens when I run this, is it basically keeps counting forever. currentNumber never stops incremementing, even once the number has been reached.
You should add currentNumber as a dependency for useEffect(). This way useEffect() will get triggered every second and a new timeout will be registered but only as long as currentNumber is smaller than number.
const Counter = ({ number }) => {
const [currentNumber, setCurrentNumber] = React.useState(0);
React.useEffect(() => {
if(currentNumber < number) setTimeout(() => setCurrentNumber(currentNumber + 1), 1000);
}, [currentNumber]);
return <span>{currentNumber}</span>;
};
ReactDOM.render(<Counter number={20}/>, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I ran the code and it's getting stuck. Reason:
You're scheduling 5 timers which will all update currentNumber to currentNumber + 1. currentNumber is 0, so it will update number times to 1 after 2 seconds. You can approach this with an interval or a useEffect:
useEffect(() => {
if (currentNumber < number)
setTimeout(() => setCurrentNumber(n => n + 1), 2000)
}, [number, currentNumber])
If you're using an interval, make sure you return a cleanup callback from useEffect, like that:
useEffect(() => {
const id = useInterval(...)
return () => clearInterval(id)
}, [...])
Please try this code:
const [currentNumber, setCurrentNumber] = useState(0);
useEffect(() => {
if (currentNumber >= number) return;
setTimeout(() => {
setCurrentNumber(currentNumber + 1);
}, 1000);
}, [currentNumber]);
return <span>{currentNumber}</span>;
I am trying to use a for loop in order to run a function 10 times, which relies on the previous state to update. I know I am not supposed to setState in a loop since state changes are batched, so what are my options if I want to run the function 10 times with a single click handler? I am using hooks (useState), if that matters.
Below is my relevant code :
export default function Rolls(props) {
const [roll, setRoll] = useState(null)
const [tenPityCount, setTenPityCount] = useState(0)
const [ninetyPityCount, setNinetyPityCount] = useState(0)
// handler for single roll
const handleSingleRoll = () => {
// sets the main rolling RNG
const rng = Math.floor(Math.random() * 1000) +1
// pulls a random item from the data set for each star rating
const randomFiveStar = Math.floor(Math.random() * fiveStarData.length)
const randomFourStar = Math.floor(Math.random() * fourStarData.length)
const randomThreeStar = Math.floor(Math.random() * threeStarData.length)
// check if ten pity count has hit
if (tenPityCount === 9) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
return;
}
// check if 90 pity count has hit
if (ninetyPityCount === 89) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
return;
}
// check if rng hit 5 star which is 0.6%
if (rng <= 6) {
setRoll(fiveStarData[randomFiveStar].name)
setNinetyPityCount(0)
// check if rng hit 4 star which is 5.1%
} else if (rng <= 51) {
setRoll(fourStarData[randomFourStar].name)
setTenPityCount(0)
// only increment for 90 pity counter b/c 10 pity resets upon hitting 4 star
setNinetyPityCount(prevState => prevState +1)
// anything else is a 3 star
} else {
setRoll(threeStarData[randomThreeStar].name)
// pity counter increment for both
setTenPityCount(prevState => prevState + 1)
setNinetyPityCount(prevState => prevState +1)
}
}
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
}
return (
<>
<div>
<span>
<button onClick={handleSingleRoll} className='btn btn-primary mr-2'>Wish x1</button>
<button onClick={handleTenRoll} className='btn btn-primary mr-2'>Wish x10</button>
<button className='btn btn-danger'>Reset</button>
</span>
</div>
</>
)
}
My suggestion would be to use a proxy functions:
const [roll, setRollState] = useState(null)
const [tenPityCount, setTenPityCountState] = useState(0)
const [ninetyPityCount, setNinetyPityCountState] = useState(0)
const newState = useRef({});
const consolidatedUpdates = useRef({setRollState, setTenPityCountState, setNinetyPityCountState})
function presetStateUpdate(name, state){
newState.current[name] = state
}
function pushPresetState(){
Object.entries(newState.current).forEach(([fnName, value]) => {
const fn = consolidatedUpdates[fnName];
if(typeof fn == 'function') fn(value);
});
newState.current = {};
}
const setRoll = v => presetStateUpdate('setRoll', v);
const setTenPityCount = v => presetStateUpdate('setTenPityCount', v);
const setNinetyPityCount = v => presetStateUpdate('setNinetyPityCount', v);
respectively:
const handleTenRoll = () => {
for (let i = 0; i < 10; i++) {
handleSingleRoll()
}
pushPresetState()
}
I am struggling with outputting the duplicated letter in this function, seems a simple one, but how can I only return a duplicated letter inside the letter function.
function letter(get) {
console.log(get) // helloworld
const split = get.split('');
const unique = split.some(function(v,i,a){
console.log(v, i, a)
});
// expected output should be 'l'
}
letter('helloworld');
First off: This is not a react specific question.
Second: Your current code won't produce your expected output. some only checks if any of the elments in an array pass a test and return true if any of them does, false otherwise. Your code also does not return any values, it just logs them to the console. There is also no logic in your code that performs any checks for duplicates or anything else.
Here is a your snippet modified to solve your problem.
function letter(get) {
let checkedLetters = "";
for (let i = 0; i < get.length; i++) {
const letter = get[i];
if (checkedLetters.includes(letter)) {
console.log(letter);
return letter;
}
checkedLetters += letter;
}
return undefined;
}
It will return the first duplicate letter in the provided string (and log it to the console), it will return undefined if there are no duplicates.
Just for the fun (since the question has its tag), I made a React component (see online demo) that displays the first pair of duplicate characters in a string. Notice the monospaced font in the CSS.
import React, { useState } from "react";
import "./styles.css";
function firstDuplicate(text) {
const arr = text.split("");
const dictionary = {};
for (let i = 0; i < arr.length; i++) {
if (dictionary[arr[i]] != null) return [dictionary[arr[i]], i];
else dictionary[arr[i]] = i;
}
return [null, null];
}
function highlightString(text, first, second) {
if (first == null || second == null) return "";
return (
" ".repeat(first) +
text[first] +
" ".repeat(second - first - 1) +
text[second]
);
}
export default function App() {
const [text, setText] = useState("");
const [first, second] = firstDuplicate(text);
const onChange = event => {
setText(event.target.value);
};
return (
<div className="App">
<h2>Type your text and we'll find the first duplicate:</h2>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start"
}}
>
<input type="text" onChange={onChange} value={text} />
<input
type="text"
value={highlightString(text, first, second)}
disabled
/>
</div>
</div>
);
}
I'm new to ReactJS and I have some problem with react function. I have simple counter that changes current number depending on the button you clicked. It works fine except the check on minimum and maximum value. Here is my code:
import React, { useState } from 'react';
export default function CounterFunction(props) {
const {min, max} = props;
const [cnt, setCnt] = useState(min);
const decrease = () => {
setCnt(set(cnt - 1));
}
const increase = () => {
setCnt(set(cnt + 1));
}
let set = function(newCnt) {
console.log("TCL: set -> newCnt", newCnt)
let cnt = Math.min(Math.max(newCnt, min), max);
setCnt(cnt);
}
return (
<div>
<button onClick={decrease}>Minus 1</button>
<strong>{cnt}</strong>
<button onClick={increase}>Plus 1</button>
</div>
)
}
And here is App component:
import React from 'react';
import MinMaxFunction from './carts/minmax';
export default function() {
return (
<div>
<MinMaxFunction min={2} max={10} />
</div>
);
}
When I try to increase or decrease number it turns into NaN. Any help would be appreciated.
const decrease = () => {
setCnt(set(cnt - 1));
}
const increase = () => {
setCnt(set(cnt + 1));
}
let set = function(newCnt) {
console.log("TCL: set -> newCnt", newCnt)
let cnt = Math.min(Math.max(newCnt, min), max);
return cnt; // return
}
You need to just return cnt from set.
In set you are setting cnt to desired value but returning nothing hence undefined. In decrease and increase you are setting cnt to return value of set which is undefined hence NaN.
Alternate way of doing same thing:
const decrease = () => {
set(cnt - 1); // call the set function, no need of setCnt here
}
const increase = () => {
set(cnt + 1);
}
let set = function(newCnt) {
console.log("TCL: set -> newCnt", newCnt)
let cnt = Math.min(Math.max(newCnt, min), max);
setCnt(cnt); // set state just here
}
Your function set is returning undefined, because you don't have an return statement there. And you are setting undefined in your setCnt.
You don't need to pass set into setCnt, because you are using setCnt inside set. So change the code to:
const decrease = () => { set(cnt - 1); }
The set function should return value which is the new number. You didn't use return so the computer assumes you're returning nothing. So you need to do return cnt;