yahoo / fluxible

A pluggable container for universal flux applications.

Home Page:http://fluxible.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

onClick event binding on NavLink component disables client-side navigation

wfsmith opened this issue · comments

Expected behavior

If bound method does not stop propagation intentionally, the method should execute and clientside navigation should function correctly.

I am also just wondering if there is a workaround. I want to add Google Analytics event tracking to various links throughout our website. E.g. navigation clicks, clicks on stories via the headline vs banner photo, etc. I have a wrapper <MyLink gaEvent={eventData} /> component which I can pass GA event data to, and I then bound an event to NavLink to trigger sending this data to GA. But the below behavior occurs and the client navigation does not execute and the binding thus does not execute either.

Actual behavior

All NavLinks, when clicked, will behave as a normal <a href> and effectively restart the application.

Information about the Issue

I am not seeing any errors or warnings.

Steps to reproduce the behavior

  1. Create a method that outputs something to console.log or other debug console.

  2. bind this method to NavLink via onClick or onTouchEnd

  3. run your application and click on the link.

@wfsmith Thanks for the report. You can see in the code here that is onClick is passed in, we will not bind the dispatchNavAction handler. Thats why you are seeing page refreshes instead of client navigation.

There could be a couple of ways to handle this:

  1. You could trigger the GA event by listening to the NAVIGATE_SUCCESS event that the navigateAction emits. You could listen to this via your store or via an App level component after the store emits a change.
  2. Another option could be to introduce a new prop that you pass in that could be triggered before or after the executeAction is called. But I'm not really in favor of this one.

Thank you redonkulus. I had considered the NAVIGATE_SUCCESS option, but I need to pass in extra data about the nature of the click (custom dimensions, GA event action/label/category), and I didn't see an option to pass extra props from the NavLink to the event listener on NAVIGATE_SUCCESS

I've given this some more thought and have a workaround I was hoping you might evaluate.

As a proof of concept, this component:

import React, { Component, PropTypes } from "react";
import { navigateAction } from "fluxible-router";

class NavLinkTrack extends Component {

  navigate(e) {
    e.preventDefault();

    // ...
    // CODE for emitting a GA tracking event goes here
    // ...

    this.context.executeAction(navigateAction, { method: 'get', url: this.props.href } );
  }

  render() {
    return <a onClick={this.navigate.bind(this)} href={this.props.href}>{this.props.children}</a>;
  }
}

NavLinkTrack.propTypes = {
  href : PropTypes.string
}

NavLinkTrack.defaultProps = {
  href : null
}

export default NavLinkTrack;

For links you want to track, you would use NavLinkTrack component instead of NavLink:

<NavLinkTrack href="/some/route">A link that is tracked</NavLinkTrack>

@wfsmith that looks like an ok approach to me. This gives you more control of custom things you need to do, while still leveraging the navigateAction.

Thanks @redonkulus. This implmentation is working in production for us. In case someone else stumbles upon this, here's the final implementation. I removed a bunch of code that was specific to our app to hopefully make it clearer.

Implementation:

<Link track={{'category': 'Navigation', 'action': 'click', 'label': 'Some Route'}} href="/some/route" className="foo">I will track your click!</Link>

Component:

import React, { Component, PropTypes } from 'react';
import { navigateAction } from 'fluxible-router';

class Link extends Component {

  constructor(props){
    super(props);
    this.state = {
      isRoute : this.isRoute(this.props.href)
    };
  }

  isRoute(url) {
    // checks to see if current url is a route or not. 
    // returns the route as a string or returns null if its not a route
  }

  navigate(e) {
    e.preventDefault();
    const {href, track} = this.props;
    if(track && track.category && track.action && track.label) {
      const {category, action, label} = track;
      if(category && action && label) {
        // send event to google here
      }
    }

    // if its a route, use fluxible routr, otherwise open a new tab
    if(this.state.isRoute) {
      this.context.executeAction(navigateAction, { method: 'get', url: href });
    }
    else {
      window.open(href, '_blank');
    }
  }

  render() {
    const {href, children, className, style} = this.props;
    if (href && href.length < 1) {
      return <span>{children}</span>;
    }
    else {
      return <a href={href} onClick={this.navigate.bind(this)} className={className} style={style}>{children}</a>;
    }
  }
}

Link.contextTypes = {
  executeAction : PropTypes.func.isRequired
};

Link.propTypes = {
  className : PropTypes.string,
  href      : PropTypes.string,
  style     : PropTypes.object,
  track     : PropTypes.shape({
    category : PropTypes.string,
    action   : PropTypes.string,
    label    : PropTypes.string
  })
};

Link.defaultProps = {
  className : null,
  href      : '',
  style     : null,
  track     : {
    category : null,
    action   : null,
    label    : null
  }
};

export default Link;

Going to close this since you found a solution.

commented

Sorry reviving this, @redonkulus could you explain the reason behind this why when onClick is passed, it's not binding the dispatchNavAction