JSFiddle - React, Tailwind, and code Playground

HTML

<div id="controls">
  <button id="undo-btn" disabled>Undo</button>
  <button id="redo-btn" disabled>Redo</button>
  <button id="clear-btn" disabled>Clear History</button>
  <button id="squash-btn" disabled>Squash unique sequence</button>
</div>

<div id="app">
  <label>Add text:</label>
  <input type="text" id="text" required><button id="save-btn">Save</button>
</div>

<div id="history"></div>

CSS

#controls,
#history,
#app { padding: 1em; }

.remove { font-size: smaller; }

JavaScript

//
// scroll down to line 230 onwards to see example code
//

const StateRewind = function (options) {
    let history = [],
        changeIndex = -1,
        onChangeCallback,
        defaultForwardBackwardCallback;

    // default options
    options = Object.assign({
        log: false,
        initialState: undefined,
    }, options || {});

    // super simple logging
    const log = function log(...data) {
        if (options.log) {
            console.info('%c[state]', 'color: limegreen; font-weight: bold;', ...data);
        }
    };

    // we expose functions later to squash state history down based on a defined comparison function
    // this is done by reducing the data to non matching, and having only the latest matching value be found
    const squashReducer = function (compare, modify, startIndex) {
        return function (accumulator, currentValue, index) {
            if (typeof startIndex != "undefined") {
                index += startIndex;
            }
            let previousValue = accumulator[index - 1];
            if (index && compare(previousValue.change, currentValue.change)) {
                if (index <= changeIndex) { // adjust the change index to match the meet the new order
                    changeIndex--;
                }
                if (typeof modify == 'function') {
                    currentValue.change = modify(previousValue.change, currentValue.change);
                }
                log('squashing index', index, ' from: ', previousValue, ' to:', currentValue); 
                accumulator[index - 1] = currentValue;
                startIndex = (startIndex || 0) - 1;
                return accumulator;
            }
            return accumulator.concat([currentValue]);
        }
    };

    // runs any provided callback after any state change (set,exec,undo,redo)
    const onChangeHandler = function () {
        if (typeof onChangeCallback == 'function') {
            onChangeCallback();
        }
    };

   ...