简体   繁体   中英

Include Modal functionality in React Higher-Order Component

I created the below HOC which I can use to wrap a React component to add 2 inactivity timers: the first to show the user a warning; the other to log them out. I got the idea from here and it seems to work pretty well. That is, I can add withTimer functionality to a component by wrapping it like this:

export default withTimer(DragDropContext(HTML5Backend)(App));

The problem is the warning alert box halts the event loop (as alert boxes apparently always do), so the logout function is never reached.

I believe a modal (eg, from react-bootstrap) would solve this, as it presumably would not halt the event loop, thus the logout would occur as intended if the user is still idle after the warning alert.

How would I change the below HOC to use a modal for the warning instead of an alert box? Is this possible? That is, can a HOC that's used to wrap another component include a component itself (ie, the modal) so as to keep it decoupled from the wrapped component itself?

import React from 'react';
import { Modal } from 'react-bootstrap';

const withTimer = (WrappedComponent) => {
  class WithTimer extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        warningTime: 5000,
        signoutTime: 10000
      };

      this.events = [
        'load',
        'mousemove',
        'mousedown',
        'click',
        'scroll',
        'keypress'
      ];

      for (var i in this.events) {
        window.addEventListener(this.events[i], this.resetTimeout);
      }

      this.setTimeout();
    }

    clearTimeoutFunc = () => {
      if (this.warnTimeout) clearTimeout(this.warnTimeout);
      if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
    };

    setTimeout = () => {
      this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
      this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
    };

    resetTimeout = () => {
      this.clearTimeoutFunc();
      this.setTimeout();
    };

    warn = () => {
      window.alert('You will be logged out soon. Click to stay logged in.');
    };

    logout = () => {
      window.alert('You are being logged out!');
      // log the user out here
    };

    render() {
      console.log('HOC');
      return <WrappedComponent {...this.props.children} />;
    }
  }
  return WithTimer;
};

export default withTimer;

If you wanted to use a Modal, you could do something like this:

Live Demo

withTimer.js

import React from 'react';
import MyModal from './MyModal';

const withTimer = (WrappedComponent) => {
  class WithTimer extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        warningTime: 5000,
        signoutTime: 10000,
        showModal: false,
        modalMessage: "",
        modalButtonText: "",
      };

      this.events = [
        'load',
        'mousemove',
        'mousedown',
        'click',
        'scroll',
        'keypress'
      ];

      for (var i in this.events) {
        window.addEventListener(this.events[i], this.resetTimeout);
      }

      this.setTimeout();
    }

    clearTimeoutFunc = () => {
      if (this.warnTimeout) clearTimeout(this.warnTimeout);
      if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
    };

    setTimeout = () => {
      this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
      this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
    };

    resetTimeout = () => {
      this.clearTimeoutFunc();
      this.setTimeout();
    };

    onModalClick = () => {
      this.setState({
        showModal: false,
      }, () => this.resetTimeout())
    }

    warn = () => {
      this.setState({
        modalButtonText: "Stay Logged In",
        modalHeader: "Warning!",
        modalMessage: 'You will be logged out soon. Click to stay logged in.',
        showModal: true,
      });
    };

    logout = () => {
      this.setState({
        modalButtonText: "Ok",
        modalHeader: "Session Timed Out",
        modalMessage: 'You are being logged out!',
        showModal: true,
      });
      // log the user out here
    };

    render() {
      console.log('HOC');
      return (
        <>
        <MyModal 
          show={this.state.showModal} 
          modalMessage={this.state.modalMessage}
          modalHeader={this.state.modalHeader}
          buttonText={this.state.modalButtonText}
          onButtonClick={this.onModalClick} />
        <WrappedComponent {...this.props.children} />
        </>
      );
    }
  }
  return WithTimer;
};

export default withTimer;

MyModal.js

import React, { useState } from "react";
import { Modal, Button } from "react-bootstrap";

function MyModal({ show = false, modalMessage, modalHeader, onButtonClick, buttonText }) {
  const handleClick = event => {
    onButtonClick(event);
  }

  return (
    <Modal show={show} onHide={handleClick} animation={false}>
      <Modal.Header closeButton>
        <Modal.Title>{modalHeader}</Modal.Title>
      </Modal.Header>
      <Modal.Body>{modalMessage}</Modal.Body>
      <Modal.Footer>
        <Button variant="primary" onClick={handleClick}>
          {buttonText}
        </Button>
      </Modal.Footer>
    </Modal>
  );
}

export default MyModal;

Yes, you can render any components you'd like in the HOC. So in your case you can render a <Modal/> .

Of course, whether the modal is displayed or not is dynamic, so that's a perfect job for the component's state to come into play. Use conditional statements in your render function to either render or not render your modal.

import React from 'react';
import { Modal } from 'react-bootstrap';

const withTimer = (WrappedComponent) => {
  class WithTimer extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        showWarning: false,
        showLogout: false,
        warningTime: 5000,
        signoutTime: 10000
      };

      this.events = [
        'load',
        'mousemove',
        'mousedown',
        'click',
        'scroll',
        'keypress'
      ];

      for (var i in this.events) {
        window.addEventListener(this.events[i], this.resetTimeout);
      }

      this.setTimeout();
    }

    clearTimeoutFunc = () => {
      if (this.warnTimeout) clearTimeout(this.warnTimeout);
      if (this.logoutTimeout) clearTimeout(this.logoutTimeout);
    };

    setTimeout = () => {
      this.warnTimeout = setTimeout(this.warn, this.state.warningTime);
      this.logoutTimeout = setTimeout(this.logout, this.state.signoutTime);
    };

    resetTimeout = () => {
      this.clearTimeoutFunc();
      this.setTimeout();
    };

    warn = () => {
      this.setState({ showWarning: true });
    };

    logout = () => {
      this.setState({ showLogout: true });
      // log the user out here
    };

    render() {
      let modal;
      if (this.state.showLogout) {
        modal = <Modal>...</Modal>;
      } else if (this.state.showWarning) {
        modal = <Modal>...</Modal>;
      } else {
        modal = null;
      }

      return <React.Fragment>
        <WrappedComponent {...this.props.children} />
        { modal }
      </React.Fragment>;
    }
  }
  return WithTimer;
};

export default withTimer;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM