<div id="app"></div>
.counter-display {
  display: flex;
  justify-content: space-between;
}

.counter-display > * {
  padding: 1rem;
}

#count-display {
  font-size: 1.2rem;
  margin: 0 1rem;
}

.main {
  max-width: 500px;
  margin: 0 auto;
}

.main > h1 {
  text-align: center;
}

.history {
  text-align: center;
  width: 50%;
  margin: 0 auto;
  margin-top: 50px;
}

.history-box {
  border: 1px solid black;
  height: 150px;
  overflow-y: auto;
}

ul.history-box {
  list-style-type: none;
  padding: 0;
}

ul.history-box li {
  display: flex;
  justify-content: space-around;
}

.controls {
  display: flex;
  justify-content: center;
  padding: 1rem;
}

.controls > button {
  padding: 1rem;
  margin: 1rem;
}

button {
  cursor: pointer;
}
/*
* https://frontendeval.com/questions/undoable-counter
*
* Create a simple counter with undo/redo functionality
*/

import { useState } from "https://cdn.skypack.dev/react@17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.1";

function Counter({ history, setHistory, count, setCount }) {
  function handleClick(num) {
    const historyObj = {
      action: num,
      before: count,
      after: count + num
    };
    setHistory([...history, historyObj]);

    setCount(historyObj.after);
  }

  return (
    <div className="counter-display">
      <button onClick={() => handleClick(-100)}>-100</button>
      <button onClick={() => handleClick(-10)}>-10</button>
      <button onClick={() => handleClick(-1)}>-1</button>
      <div id="count-display">{count}</div>
      <button onClick={() => handleClick(1)}>+1</button>
      <button onClick={() => handleClick(10)}>+10</button>
      <button onClick={() => handleClick(100)}>+100</button>
    </div>
  );
}

function History({ history }) {
  const histories = history.map((historyObj, index) => {
    return (
      <li key={index}>
        <span>{historyObj.action}</span>
        <span>{`${historyObj.before} -> ${historyObj.after}`}</span>
      </li>
    );
  });

  return (
    <div className="history">
      <h2>History</h2>
      <ul className="history-box">{histories}</ul>
    </div>
  );
}

function App() {
  const [history, setHistory] = useState([]);
  const [redos, setRedos] = useState([]);
  const [count, setCount] = useState(0);

  function handleUndoClick() {
    if (history.length === 0) {
      return;
    }
    const newHistory = history.slice(0); // pop
    redos.push(newHistory.pop());
    setRedos(redos);
    setHistory(newHistory);
    if (newHistory.length !== 0) {
      setCount(newHistory[newHistory.length - 1].after);
    } else {
      setCount(0);
    }
  }

  function handleRedoClick() {
    if (redos.length === 0) {
      return;
    }
    const redo = redos.pop();
    const newHistory = [...history, redo];
    setHistory(newHistory);
    setRedos(redos);
    setCount(newHistory[newHistory.length - 1].after);
  }

  return (
    <div className="main">
      <h1>Undoable counter</h1>
      <div className="controls">
        <button onClick={handleUndoClick}>Undo</button>
        <button onClick={handleRedoClick}>Redo</button>
      </div>
      <Counter
        history={history}
        setHistory={setHistory}
        count={count}
        setCount={setCount}
      />
      <History history={history} />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('app'));
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js