<div id="app"></div>
.flex-row {
  display: flex;
  flex-direction: row;
  margin-bottom: 0.5rem;
  justify-content: center;
}

.flex-row > :not(:last-child) {
  margin-right: 1rem;
}

.counter {
  text-align: center;
  max-width: 30rem;
  margin: 0 auto;
}

.counter__value {
  min-width: 2rem;
  text-align: right;
}

.history__list {
  border: solid 1px black;
  padding: 0.5rem;
}
/*
 * https://frontendeval.com/questions/undoable-counter
 *
 * Create a simple counter with undo/redo functionality
 */

import React, { useState, useEffect } from "https://esm.sh/react@18.2.0";
import ReactDOM from "https://esm.sh/react-dom@18.2.0";
import * as TypesReact from "https://cdn.skypack.dev/@types/react";
import * as TypesReactDom from "https://cdn.skypack.dev/@types/react-dom";

const plusOrMinus = (number: number): string =>
  number >= 0 ? "+" + number.toString() : number.toString();

interface HistoryProps {
  history: number[];
  currentCount: number;
}

const History = ({ history, currentCount }: HistoryProps) => {
  let count = currentCount;
  return (
    <div class="history">
      <h2>History</h2>
      <ul class="history__list">
        {[...history].reverse().map((h) => {
          const output = count;
          count -= h;
          const change = `${count} -> ${output}`;
          return (
            <li className="flex-row">
              <span>{plusOrMinus(h)}</span>
              <span>{change}</span>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

interface CounterAdjustProps {
  number: number;
  handler: (n: number) => void;
}

const CounterAdjust = ({ number, handler }: CounterAdjustProps) => {
  return <button onClick={() => handler(number)}>{plusOrMinus(number)}</button>;
};

const Counter = () => {
  const [count, setCount] = useState(0);
  const [history, setHistory] = useState<number[]>([]);
  const [undone, setUndone] = useState<number[]>([]);

  const adjust = (n: number): void => {
    setHistory((h) => [...h, n].slice(-50));
    setCount((last) => last + n);
  };

  const handleAdjust = (n: number): void => {
    adjust(n);
    setUndone([]);
  };

  const undo = (): void => {
    const n = history[history.length - 1];
    if (n === undefined) return;
    setCount((last) => last - n);
    setHistory((h) => h.slice(0, -1));
    setUndone((u) => [...u, n]);
  };

  const redo = (): void => {
    const n = undone[undone.length - 1];
    adjust(n);
    setUndone((u) => u.slice(0, -1));
  };

  // load saved state
  useEffect(() => {
    const state = window.localStorage.getItem("state");
    if (!state) return;
    const { count, history, undone } = JSON.parse(state);
    setCount(count);
    setHistory(history);
    setUndone(undone);
  }, []);

  // save state on change
  useEffect(() => {
    const state = JSON.stringify({ count, history, undone });
    window.localStorage.setItem("state", state);
  }, [count, history, undone]);

  return (
    <div className="counter">
      <h1>Undoable counter</h1>
      <div className="flex-row">
        <button onClick={undo}>Undo</button>
        <button onClick={redo} disabled={undone.length === 0}>
          Redo
        </button>
      </div>
      <div className="flex-row">
        <CounterAdjust number={-100} handler={handleAdjust} />
        <CounterAdjust number={-10} handler={handleAdjust} />
        <CounterAdjust number={-1} handler={handleAdjust} />
        <span className="counter__value">{count}</span>
        <CounterAdjust number={1} handler={handleAdjust} />
        <CounterAdjust number={10} handler={handleAdjust} />
        <CounterAdjust number={100} handler={handleAdjust} />
      </div>
      {history.length ? (
        <History history={history} currentCount={count} />
      ) : null}
    </div>
  );
};

const App = () => {
  return <Counter />;
};

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

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.