简体   繁体   中英

Modal component with fade-in overlay and dialog disabled until animation ends

I'm trying to rewrite one of my JS plugins to react, as a way of learning.

I have a panel that when hidden/shown needs to be updated with several classnames as well as some that need to wait for a css animation to complete (why the timer).

How should I do this in a react way? Using querySelector to change classnames seem very wrong..?

Detailed explanation

When showPanel is triggered the following need to happen

  • the body/html element need updated css (hence me adding classes)
  • an existing overlay fades in (adding a class to that)
  • the modal div is displayed (adding a class for that)
  • the modal div is told to be active AFTER the animation has been run (hence the timer and class "am-animation-done")

What I preferably would like to have/learn is best practice to do this in reactjs. I'm thinking a toggle state that when triggered sets the state to visible/hidden and if set to "visible" the class changes below happens. My biggest issue is the timer thing.

  showPanel = () => {
            document.querySelector('body').classList.add('am-modal-locked');
            document.querySelector('html').classList.add('am-modal-locked');
            document.querySelector('.am-overlay').classList.add('fadein');
            document.querySelector('.am-modal').classList.add('am-show');

            const timer = setTimeout(() => {
                document.querySelector('.am-modal').classList.add('am-animation-done');
            }, 500);
            return () => clearTimeout(timer);
  };

  hidePanel = () => {
            document.querySelector('.am-modal').classList.remove('am-show');
            document.querySelector('.am-modal').classList.remove('am-animation-done');
            document.querySelector('.am-overlay').classList.add('fadeout');

            const timer = setTimeout(() => {
                document.querySelector('.am-overlay').classList.remove('fadein');
                document.querySelector('.am-overlay').classList.remove('fadeout');
                document.querySelector('body').classList.remove('am-modal-locked');
                document.querySelector('html').classList.remove('am-modal-locked');
            }, 500);
            return () => clearTimeout(timer);
    };

Source code updated for clarifaction

This is a lot simpler in React, here's an example with hooks

function Panel() {
  const [hidden, setHidden] = useState(false);
  const toggleCallback = useCallback(() => setHidden(hidden => !hidden), []);

  const cls = hidden ? 'hide' : 'show';
  return (
    <div className={cls}>
      <button onClick={toggleCallback}>Toggle</>
    </div>
  )
}

Yes that's not a very good way to do it. Instead you should use state variables to toggle your classes as well. There is no need to manually manipulate DOM. The you can set up your timeout inside the callback of your first setState to change state again.

Maybe something like this:

class Todo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      class1: 'on',
      class2: 'off'
    }
  }

  toggle = () => {
    this.setState({class1: 'off'}, () => {
      setTimeout(() => {
        this.setState({class2: 'on'})
      }, 2000)
    })
  }

  render() {
    const {class1, class2} = this.state;

    return (
      <div>
        <h1 className={`${class1} ${class2}`} onClick={this.toggle}>Class toggle</h1>
      </div>
    )
  }
}

You can use the state to dinamically change classnames inside your component

className={this.state.isPanelVisible}

And maybe instead of setting it as boolean you can set your variable to the class you need at the moment.

React working with virtual DOM so you should play with state and change class of that particular element like below example:

constructor(props) {
        super(props);
        this.state = {'active': false, 'class': 'album'};
    }

  handleClick(id) {
    if(this.state.active){
      this.setState({'active': false,'class': 'album'})
    }else{
      this.setState({'active': true,'class': 'active'})
    }
  }

<div className={this.state.class}  onClick={this.handleClick.bind(this.data.id}>
    <p>Data</p>
</div>

In very basic use cases you can write the logic inside of the class itself.

<div className={active ? "active" : "disabled"} />

In more advanced cases I would suggest to use something like classnames package. https://www.npmjs.com/package/classnames

<div className={classNames({ foo: true, bar: true, boo: false })} />

Which would result in div having class foo and bar

This is mainly regarding one component, but if you really have to affect class of something so far away as body would be, than you are most likely gonna need useQuerySelector or put the state somewhere high and then base the logic on it.

使用此方法更改状态更改的样式

<div className={`rest_of_classes ${isClassChange ? 'change_class_name': ''}`} />

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