webdev
webdev

Reputation: 1

How to limit and control the number of click on button per day or per hour in ReactJs

How to limit and control the number of click on button per day (or per hour) in ReactJs

any help please

Upvotes: 0

Views: 963

Answers (1)

jsejcksn
jsejcksn

Reputation: 33796

How to limit and control the number of click on button per day (or per hour) in ReactJs

You can't restrict this in React. I explained this in a comment:

This is something that you must validate in your private code (back end / server / etc.). You must assume that the user of your client (React) app has complete control over the view, state (JavaScript), network requests, etc., so any attempts to restrict actions on the client can be subverted/bypassed by the user.

So, you'll need to validate this in other private code that's not running in your client app.


With that out of the way, and acknowledging that the user can manipulate the state of your client app, let's explore an implementation of the kind of quota you asked about on the client (just for fun) with a custom hook: useQuota:

Note that the hook doesn't use timers directly because timers on the order of hours/days are imprecise/unreliable.

Code in TypeScript Playground

<div id="root"></div><script src="https://unpkg.com/[email protected]/umd/react.development.js"></script><script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/[email protected]/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">

// import ReactDOM from 'react-dom/client';
// import {
//   StrictMode,
//   useCallback,
//   useRef,
//   useState,
//   type ReactElement,
// } from 'react';

// This Stack Overflow snippet demo uses UMD modules
// instead of the commented import statments above
const {
  StrictMode,
  useCallback,
  useRef,
  useState,
} = React;

type QuotaUsageResult = {
  /**
   * When your remaining value will increase next.
   * Will always be a Date unless your quota limit is `0`.
   */
  nextIncrease: Date | null;

  /** Non-negative integer */
  remaining: number;

  /** Whther or not the quota usage succeeded */
  success: boolean;
};

type QuotaCallback = (result: QuotaUsageResult) => void;

type QuotaOptions = {
  /** Milliseconds. e.g. 10 seconds = `10_000` */
  expirationInterval: DOMHighResTimeStamp;

  /** Non-negative integer */
  limit: number;
};

function useQuota (
  {expirationInterval, limit}: QuotaOptions,
  callback: QuotaCallback,
): () => void {
  const [timeStamps, setTimeStamps] = useState<EpochTimeStamp[]>([]);

  return useCallback(() => {
    const now = Date.now();
    const timeStampLimit = now - expirationInterval;
    const notExpired = [...timeStamps.filter(ts => ts > timeStampLimit), now];
    const quotaExceeded = notExpired.length > limit;
    if (quotaExceeded) while (notExpired.length > limit) notExpired.pop();
    setTimeStamps(notExpired);

    const [oldest] = notExpired;
    const nextIncrease = oldest ? new Date(now + oldest - timeStampLimit) : null;

    const result: QuotaUsageResult = {
      nextIncrease,
      remaining: limit - notExpired.length,
      success: !quotaExceeded,
    };

    callback(result);
  }, [callback, limit, expirationInterval, timeStamps]);
}

function App (): ReactElement {
  const timerIdRef = useRef(0);

  const [buttonText, setButtonText] = useState('Next');
  const [info, setInfo] = useState('Click the button to begin');

  // Set the limit to 4 actions, every 10s
  const onClick = useQuota({expirationInterval: 10e3, limit: 4}, (result) => {
    const {nextIncrease, remaining, success} = result;

    // Cancel previous timer
    clearTimeout(timerIdRef.current);

    // Indicate whether actions remain
    setButtonText(remaining === 0 ? 'Wait' : 'Next');

    let updatedInfo = `Success: ${success ? 'true' : 'false'}`;
    updatedInfo += `, Remaining: ${remaining}`;

    if (nextIncrease) {
      updatedInfo += `, Next increase at: ${nextIncrease.toLocaleTimeString()}`;
      // Set a timer to update the button text the next time the quota increases
      const msUntilNextIncrease = nextIncrease.getTime() - Date.now();
      timerIdRef.current = setTimeout(() => setButtonText('Next'), msUntilNextIncrease);
    }

    setInfo(updatedInfo);
  },
);

  return (
    <div>
      <div>{info}</div>
      <button {...{onClick}}>{buttonText}</button>
    </div>
  );
}

const reactRoot = ReactDOM.createRoot(document.getElementById('root')!);

reactRoot.render(
  <StrictMode>
    <App />
  </StrictMode>
);

</script>

Upvotes: 1

Related Questions