How to defer this.props.onClick() until after a CS

2019-07-27 07:05发布

I am calling a custom NanoButton component from my page along with an onClick instruction to route to another page:

// Page.js
import { Component } from 'react';
import Router from 'next/router';
class Page2 extends Component {
  render() {
    return(
      <NanoButton type="button" color="success" size="lg" onClick={() => Router.push('/about')}>About</NanoButton>
    )
  }
}

When the button (NanoButton component) is clicked, I want to execute some internal code before moving on to the onClick coming in as props. Through this internal code, I am trying to simulate the material-design ripple effect that lasts 600 milliseconds. This is how I do it:

import { Component } from 'react';
import { Button } from 'reactstrap';

class NanoButton extends Component {
  constructor(props) {
    super(props);
    this.onClick = this.onClick.bind(this);
  }
  onClick(e) {
    this.makeRipple(e);
    this.props.onClick();
  }
  makeRipple(e) {
    const elements = e.target.getElementsByTagName('div');
    while (elements[0]) elements[0].parentNode.removeChild(elements[0]);
    const circle = document.createElement('div');
    e.target.appendChild(circle);
    const d = Math.max(e.target.clientWidth, e.target.clientHeight);
    circle.style.width = `${d}px`;
    circle.style.height = `${d}px`;
    const rect = e.target.getBoundingClientRect();
    circle.style.left = `${e.clientX - rect.left - (d / 2)}px`;
    circle.style.top = `${e.clientY - rect.top - (d / 2)}px`;
    circle.classList.add('ripple');
  }
  render() {
    return (
      <Button
        className={this.props.className}
        type={this.props.type}
        color={this.props.color}
        size={this.props.size}
        onClick={this.onClick}
      >
        {this.props.children}
      </Button>
    );
  }
}

export default NanoButton;

So as you can see, I need the makeRipple method to execute before this.props.onClick. And initially, it didn't seem to doing that. However, after further testing, it turns out that the methods do run in the right order after all, except the routing (as coded in this.props.onClick) happens instantly and the ripple animation that's styled to last 600 milliseconds doesn't get a chance to run. The CSS that makes this animation happen is:

button {
    overflow: hidden;
    position: relative;
}

button .ripple {
    border-radius: 50%;
    background-color: rgba(255, 255, 255, 0.7);
    position: absolute;
    transform: scale(0);
    animation: ripple 0.6s linear;
}

@keyframes ripple {
    to {
        transform: scale(2.5);
        opacity: 0;
    }
}

How do I make the this.props.onClick run only AFTER the animation is complete? I tried setting a timeout like so:

setTimeout(this.props.onClick(), 600);

But that throws an error.

Note: I'm using NextJS for server side rendering, if that makes any difference.

1条回答
SAY GOODBYE
2楼-- · 2019-07-27 07:51

There are a lot of ways to do it, like Promise, async/await, etc. But if you try setTimeout please use

setTimeout(() => this.props.onClick(), 600);

or

setTimeout(this.props.onClick, 600);

your case:

setTimeout(this.props.onClick(), 600);

won't work because this line will pass the result of this.props.onClick() into the first param instead of passing the whole function.

查看更多
登录 后发表回答