Can I Fastclick ReactJS running in Cordova
Asked Answered
Y

6

31

Does fastclick work with ReactJS's event system? It doesn't seem to be taking when run through Cordova onto iOS or Android. If not, is there another way of getting the same results. My app has no double-tap functionality so I'd like to remove that delay across the board, if possible...

Youngster answered 20/6, 2014 at 21:20 Comment(0)
N
29

Edit

Facebook decided to not add support for defining custom event types and recommend you to use something like react-tappable so you can write something like <Tappable onTap={}>.


Facebook's working on a solution in the form of TapEventPlugin, but it won't be made available until they make some decisions.

If you're reading this you're probably working on a project that can't wait until they figure out how they want to publish it.

This repo is for you: https://github.com/zilverline/react-tap-event-plugin

When Facebook solves #436 and #1170, this repo will disappear.

This solution works for React 0.14.x and 15.x.

npm i -S react-tap-event-plugin

Example of usage:

var React = require("react");
var ReactDOM = require("react-dom");
injectTapEventPlugin = require("react-tap-event-plugin");
injectTapEventPlugin();

var Main = React.createClass({
  render: function() {
    return (
      <a
        href="#"
        onTouchTap={this.handleTouchTap}
        onClick={this.handleClick}>
        Tap Me
      </a>
    );
  },

  handleClick: function(e) {
    console.log("click", e);
  },

  handleTouchTap: function(e) {
    console.log("touchTap", e);
  }
});

ReactDOM.render(<Main />, document.getElementById("container"));

Note that with the injector, you will probably need to use only onTouchTap (and not onClick anymore).

Neuburger answered 1/12, 2015 at 8:7 Comment(5)
FYI, onTouchTap works as a click (for non touchscreen).Neuburger
So just to be clear should I use react-tap-event-plugin or React-Tappable now?Maxwellmaxy
Both works, but react-tappable seems a more reasonable choice. No monkey patching.Neuburger
Thanks, probably going that way as react-tap-event-plugin seems to break down with [email protected]Maxwellmaxy
Is this solution required only for mobile browsers?Recognizor
P
17

I got FastClick to work with React, in a Webpack project. A few things seem finicky but it mostly works. (Update: only a toggle switch that was simulating clicks on a hidden checkbox was finicky -- that would be a problem regardless of React). Here's how I turned it on:

npm install -S fastclick

In main.jsx:

import FastClick from 'fastclick';

window.addEventListener('load', () => {
  FastClick.attach(document.body);
});

So even if you're not using Webpack or Browserify, I'm guessing as long as you can run the load event listener, you'll be fine.

Pancreas answered 19/5, 2015 at 4:25 Comment(5)
This worked perfect for me. FWIW, in my isomorphic react app built with webpack, I had to stub out the fastclick module on the server build with new webpack.NormalModuleReplacementPlugin(/fastclick$/i, 'node-noop')Hoodoo
@Hoodoo cool, that will be good to know if I have to go isomorphic!Pancreas
Soo... I just found out that unfortunately this solution doesnt play nicely with using React's onClick handlers. It only works for your generic html elements -- anchors, buttons, etc. Here's the best I've found: github.com/zilverline/react-tap-event-pluginHoodoo
What behavior are you seeing? I haven't had any problem with onClick handlers.Pancreas
Are you creating a React component with an onClick prop but not passing it down to the component whatever render() returns, so that it ultimately winds up on a real HTML component? As far as I know, an onClick handler on a React component won't do anything, you have to pass it down to an HTML element.Pancreas
G
5

We recently created a React component that is similar to fastclick, except that it’s much simpler and requires a manual callback. It’s pretty short so I’ll post it here:

React.initializeTouchEvents(true)

var TouchClick = React.createClass({

  defaults: {
    touched: false,
    touchdown: false,
    coords: { x:0, y:0 },
    evObj: {}
  },

  getInitialState: function() {
    return this.defaults
  },

  handler: function() {
    typeof this.props.handler == 'function' && this.props.handler.apply(this, arguments)
  },

  getCoords: function(e) {
    if ( e.touches && e.touches.length ) {
      var touch = e.touches[0]
      return {
        x: touch.pageX,
        y: touch.pageY
      }
    }
  },

  onTouchStart: function(e) {
    this.setState({ 
      touched: true, 
      touchdown: true,
      coords: this.getCoords(e),
      evObj: e
    })
  },

  onTouchMove: function(e) {
    var coords = this.getCoords(e)
    var distance = Math.max( 
      Math.abs(this.state.coords.x - coords.x), 
      Math.abs(this.state.coords.y - coords.y) 
    )
    if ( distance > 6 )
      this.setState({ touchdown: false })
  },

  onTouchEnd: function() {
    if(this.state.touchdown)
      this.handler.call(this, this.state.evObj)
    setTimeout(function() {
      if ( this.isMounted() )
        this.setState(this.defaults)
    }.bind(this), 4)
  },

  onClick: function() {
    if ( this.state.touched )
      return false
    this.setState(this.defaults)
    this.handler.apply(this, arguments)
  },

  render: function() {
    var classNames = ['touchclick']

    this.props.className && classNames.push(this.props.className)
    this.state.touchdown && classNames.push('touchdown')

    return React.DOM[this.props.nodeName || 'button']({
      className: classNames.join(' '),
      onTouchStart: this.onTouchStart,
      onTouchMove: this.onTouchMove,
      onTouchEnd: this.onTouchEnd,
      onClick: this.onClick
    }, this.props.children)
  }
})

Just pass handler prop as the callback and wrap your content inside. This also works for systems that has both touch and click events (like newer windows 8 laptops). Example:

 <TouchClick handler={this.clickHandler} className='app'>
   <h1>Hello world</h1>
 </TouchClick>
Getaway answered 21/6, 2014 at 20:11 Comment(1)
I mean, this is not a wham-bam-thank-you-maam type solution that instantly gets rid of the annoying click delay for everything on the page, is it? That's the point of FastclickPancreas
P
3

I had issues using David`s method, so as an alternative to FastClick, I implemented a mixin using HammerJs for the event. A bit more code to setup the event, but works fine.

var HammerClickMixin = React.createClass({
    componentWillMount: function() {
        this.listeneres = [];

    },
    addTapEvent: function(element,callback) {
        var mc = new Hammer.Manager(element);
        mc.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));
        mc.add(new Hammer.Tap({ event: 'tap', taps: 1 }));
        mc.on('tap',callback);
        this.listeneres.push(mc);
    },
    addDoubleTap : function(element,callback){
        var mc = new Hammer.Manager(element);
        mc.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));
        mc.on('doubletap',callback);
        this.listeneres.push(mc);
    },
    componentWillUnmount: function() {
        for(var i= 0; i < this.listeneres.length; i++){
            this.listeneres[i].destroy();
        }
    }
});

This can then be used as as followed:

 var Component = React.createClass({
          mixins: [HammerClickMixin],
          componentDidMount: function () {

              this.addTapEvent(this.refs.elementToClick.getDOMNode(),function(){
                  //Handle fast hammer tap!

              });
          },
          render: function () {
               return (
                        <div ref="elementToClick"/>                
                   );
          }
      });
Povertystricken answered 11/9, 2014 at 12:51 Comment(0)
R
2

It seemed to be working fine in my Cordova app, but there is one significant problem I ran into.

When an element is clicked using React+FastClick, and the next rendered view contains a clickable element in the same position, a onTouchEnd event is also registered in the second element.

I ditched FastClick as I don't want to align my buttons to avoid unwanted behaviour, but I need something to replace it as the click delay feels pretty bad.

Remontant answered 17/7, 2014 at 11:44 Comment(5)
I had similar problems, where a button would be clicked immediately, but then in some environments (android browsers) would be clicked again 300ms later. The tap event plugin used in react touch libs adds a nice onTouchTap callback but it doesn't work on non-touch inputs. Making a React event plugin might be the way to go though. I still haven't found the answer...Youngster
Thanks for the info, in fact after ditching FastClick I'm still running into similar "tap registers two times" problems, albeit in different parts of the app than previously. Haven't found a solution yet either...Remontant
@PirkkaEsko so FastClick wasn't the cause of the onTouchEnd issue you were seeing?Pancreas
Honestly I don't remember the details anymore, but my comment above would suggest that FastClick was not the root cause but could have something to do with them, as behaviour was altered (issues were happening in different part of the app after removing FastClick).Remontant
I found this behavior with both fastclick and react-tap-event-plugin. Still haven't found a good solution.Youngster
G
2

You can also use react-fastclick(https://github.com/JakeSidSmith/react-fastclick) from npm:

npm i react-fastclick --save

Using it you don't have to change any of your code and it works really nice! You only have to require it once.

require('react-fastclick');
Gifferd answered 14/7, 2015 at 16:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.