PAD + React

Counter with history

The start value comes from PHP in a data attribute; the step size, the undo history and the statistics are React state.

data-* useState
Live

How it is built

PAD, on the server React, in the browser

<div id="counter-app" data-initial-count="{$initialCount}"></div>

<script type="text/babel" src="{$padRoot}react/examples/counter.js"></script>
<?php

  // The start value comes from the server; everything after it is React state.

  $initialCount = 10;

?>
// The start value is the one PHP gave: data-initial-count, read through dataset. The count,
// the step, the history for undo and the statistics are state of this component only.

function CounterApp() {
  const initialCount = parseInt(document.getElementById('counter-app').dataset.initialCount, 10);

  const [count, setCount] = React.useState(initialCount);
  const [step, setStep] = React.useState(1);
  const [history, setHistory] = React.useState([initialCount]);

  const go = value => {
    setCount(value);
    setHistory([...history, value]);
  };

  const undo = () => {
    const back = history.slice(0, -1);
    setHistory(back);
    setCount(back[back.length - 1]);
  };

  const highest = Math.max(...history), lowest = Math.min(...history);

  return (
    <div className="cols-2">
      <div style={{ textAlign: 'center' }}>
        <div key={count} className="pop" style={{ fontSize: 96, fontWeight: 900, letterSpacing: '-.04em', lineHeight: 1.1,
             background: 'linear-gradient(100deg, var(--pad), var(--react))', WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent' }}>
          {count}
        </div>

        <div style={{ display: 'flex', gap: 10, justifyContent: 'center', margin: '18px 0' }}>
          <button className="btn-danger" onClick={() => go(count - step)}>− {step}</button>
          <button onClick={() => go(count + step)}>+ {step}</button>
        </div>

        <label className="small" style={{ display: 'flex', gap: 12, alignItems: 'center', justifyContent: 'center' }}>
          step
          <input type="range" min="1" max="10" value={step} onChange={event => setStep(Number(event.target.value))} />
          <strong style={{ width: 20 }}>{step}</strong>
        </label>

        <div style={{ display: 'flex', gap: 10, justifyContent: 'center', marginTop: 18 }}>
          <button className="btn-ghost btn-sm" onClick={undo} disabled={history.length <= 1}>↶ Undo</button>
          <button className="btn-ghost btn-sm" onClick={() => go(initialCount)}>Back to {initialCount}</button>
        </div>
      </div>

      <div>
        <div className="grid" style={{ gridTemplateColumns: 'repeat(2, 1fr)', gap: 10 }}>
          {[['From PHP', initialCount], ['Changes', history.length - 1], ['Highest', highest], ['Lowest', lowest]].map(([label, value]) => (
            <div key={label} className="panel">
              <div className="panel-title">{label}</div>
              <div style={{ font: '800 24px var(--mono)' }}>{value}</div>
            </div>
          ))}
        </div>
        <div className="panel" style={{ marginTop: 10 }}>
          <div className="panel-title">History</div>
          <div className="chips">
            {history.slice(-24).map((value, index, shown) => (
              <span key={history.length - shown.length + index} className={'badge' + (index === shown.length - 1 ? ' is-react' : '')}>{value}</span>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('counter-app')).render(<CounterApp />);