My custom hook useTyper.js
ended up as a bit of a mess and hard to read.
It's mimicking a nested for-loop but using React.useRef
s to hold on to the indexes between renders. The indexes are the current positions of the outer and inner loop, those often named i
and j
in traditional nested for loops.
Here's a fancy code sandbox for the mess below.
One thing I'm particularly unhappy with is the naming of stringIdx
and stringsIdx
. Any suggestion for a change?
useTyper.js
import React from "react";
import useInterval from "./useInterval.js";
/**
* A simple typewriter that prints strings, one character after another
* @param {Array<string>} strings - Array of strings to type
* @param {function<string>} set - A useState setter to update the current string value
* @param {number} defaultDelay - Delay between updates
*/
const useTyper = (strings, set, defaultDelay = 100) => {
const [delay, setDelay] = React.useState(defaultDelay);
// index counters, normally named i and j if this were an imperative loop
const stringIdx = React.useRef(0);
const stringsIdx = React.useRef(0);
const textTyper = () => {
const currentString = strings[stringsIdx.current];
set(currentString.substr(0, stringIdx.current + 1));
if (stringIdx.current > currentString.length) {
// Done with string
stringsIdx.current += 1;
stringIdx.current = 0;
if (stringsIdx.current > strings.length - 1) {
setDelay(null); // Ended.
}
}
stringIdx.current += 1;
};
useInterval(textTyper, delay);
};
export default useTyper;
For completion, this is the useInterval
hook being used by useTyper
.
It can be referenced from one of Dan Abramovs blog posts.
useInterval.js
import React from 'react';
const useInterval = (callback, delay) => {
const savedCallback = React.useRef();
// Remember the latest callback.
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
React.useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => {
clearInterval(id);
};
}
}, [delay]);
};
export default useInterval;
Here is how the useTyper
hook can be used.
import React, { useState } from "react";
import "./styles.css";
import useTyper from "./hooks/useTyper.js";
export default function App() {
const [placeHolder, setPlaceHolder] = useState("");
const placeHolders = [
"First input suggestion",
"Second input suggeston",
"Creativity flows... ",
"Alright, lets leave it here."
];
useTyper(placeHolders, setPlaceHolder);
return (
<div className="App">
<div className="container">
<input
id="formElement"
autoComplete="off"
autoFocus
size="20"
type="text"
placeholder={placeHolder}
/>
</div>
</div>
);
}
Again, here's the link to the code sandbox.