Slow Printer

Print text submitted by the user one word at a time.


Environment

Use the environment you are most comfortable with. I recommend using create-react-app to create a local version of the project so that you can inspect the DOM easily from your browser when debugging. Alternatively, you can work from a blank React template in CodeSandbox. Starter code is provided after the problem specification.

Specification

Write a functional component that accepts an extended piece of text from the user and prints the text to the screen, beginning with the first word and appending the next word every half-second until the entire text is displayed on the screen. For example, if the user submits “Hi my name is Bob”, the screen should read “Hi”, then “Hi my”, then “Hi my name”, and so on. If the user submits another piece of text reset the display and begin printing the new text. An image of the component taken as it was printing the input “Hi my name is Bob” is shown below:

The finished component
The finished component

Starter Code

Here is some code to get you started:

import React, { useState, useEffect } from "react";

function App() {
  return <WordByWord />;
}

const WordByWord = () => {
  // YOUR CODE HERE
};

export default App;

Feel free to divide the challenge into multiple subcomponents, though.

Solution

import React, { useState } from "react";
import { useEffect } from "react";

function App() {
  return <WordByWord />;
}

const WordByWord = () => {
  const [input, setInput] = useState("");
  const [text, setText] = useState([]);
  const [word, setWord] = useState(1);

  const onSubmit = (e) => {
    e.preventDefault();
    const wordsArray = input.split(" ");
    if (wordsArray.length <= 1) {
      alert("Enter more than one word.");
    } else {
      setText(wordsArray);
      setWord(1);
      setInput("");
    }
  };

  useEffect(() => {
    if (text && word < text.length) {
      const time = setTimeout(() => setWord(word + 1), 500);
      return () => clearTimeout(time);
    }
  }, [text, word]);

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
      }}
    >
      <form onSubmit={onSubmit}>
        <input
          required
          type={"text"}
          value={input}
          onChange={(e) => setInput(e.target.value)}
        />
      </form>
      <div style={{ display: "flex", flexDirection: "row", flexWrap: "wrap" }}>
        {text.slice(0, word).map((item) => (
          <p style={{ paddingRight: 4 }}>{item}</p>
        ))}
      </div>
    </div>
  );
};

export default App;

The functional component in the solution above sets up three state variables, one to hold the input of the text field as the user enters it, another to hold an array of words generated from the user’s input, and finally, an index to store the last word that should currently be on display. When the user submits a piece of text, we make use of Javascript’s native split() function to create an array of words (the function separates the string by the parameter given — in our case, a space). So long as the text is more than one word, we update our state and rely on useEffect() to increment word after a delay. useEffect() runs when the component first renders and after any variable in the second argument (the dependency array) is updated. Finally, the component achieves the growing text feature by using Javascript’s slice() function to isolate only the words that should currently be shown. As a related challenge, try to also display the text in a window that automatically scrolls at a constant, slow rate. In other words, try to mimic a teleprompter.


useState
useEffect