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:
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.
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.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>;
};