d-Threeact: How the Sift Science Console Team Made d3 and React the Best of Friends

A little less than a year ago, the Sift Science Console Team decided to migrate its Backbone and Marionette Views to ReactJS (see also our post on specific React tips we learned along the way). Among the many facets of a piece-by-piece migration like this was figuring out how best to manage (err...'reactify') our d3 charts. There were already a couple good reads/listens on this topic—with very different views on the responsibilities of each library—which we found quite helpful in establishing our own approach.

Ultimately, we decided that our d3 code should be isolated from our React code and only be available to React via a simple React Component interface. This way, we could take advantage of React's one-way data flow into the component but still let the powerful d3 take care of all things svg-related (layout, data-binding, transitions, etc). So our ideal <Chart /> component would look like:

<Chart
  type=''
  data={}
  options={}
/>

With this setup, we could plant this generic <Chart /> component all over our codebase, supplying just the type of chart we want to produce and the data we want to display, and let d3 take care of the rest. Also, the <Chart /> component would have no notion of state with regards to data!

Okay, so now let's work backwards from our end goal to figure out how we're going to get there!

The Chart Component

First, we need to create the Chart component and integrate d3’s key selection operations (enter, update, exit) into React’s lifecycle methods. Luckily, as Nicolas Hery pointed out, that integration happens pretty naturally. Here’s a first pass at Chart.jsx:

// chart.jsx
module.exports = React.createClass({
  propTypes: {
    type: React.PropTypes.string.isRequired,
    data: React.PropTypes.array.isRequired,
    options: React.PropTypes.object
  },
  
  componentDidMount() {
    // create chart and do first data bind
  },
  
  componentDidUpdate() {
    // update chart with new data
  },
  
  componentWillUnmount() {
    // cleanup after chart
  },
  
  render() {
    return (
      <div className={'sift-chart ' + _.dasherize(this.props.type)}></div>
    );
  }
});

Note that we don't actually need to remove the chart in componentWillUnmount, since those DOM elements are being unmounted by React, but we'll want to detach anything the chart is tied to.

SiftChartFactory

Okay, so now that we have a skeleton for our component, we need a way to create a chart out of our data given a chart type. This is an excellent use case for the Factory Pattern. Here's our first pass at a simple SiftChartFactory:

// sift_chart_factory.js
var MyAwesomeScatterPlot = require('./my_awesome_scatterplot'),
    MyEvenCoolerBarGraph = require('./my_even_cooler_bar_graph');

SiftChartFactory = function(type) {
  var newChart;
  
  // throw an error if the chart type doesn't exist
  if (typeof SiftChartFactory[type] !== 'function') {
    throw new Error(type + ' is not a valid Sift Chart!');
  }
  
  newChart = new SiftChartFactory[type]();

  return newChart;
};

// attach all chart types as static properties
SiftChartFactory.MyAwesomeScatterPlot = MyAwesomeScatterPlot;
SiftChartFactory.MyEvenCoolerBarGraph = MyEvenCoolerBarGraph;

module.exports = SiftChartFactory;

Putting aside our individual chart constructors, we can flesh out the <Chart /> component a little more with what we want out of the SiftChartFactory interface:

// Chart.jsx
SiftChartFactory = require('./sift_chart_factory');

module.exports = React.createClass({
  propTypes: {
    type: React.PropTypes.string.isRequired,
    data: React.PropTypes.array.isRequired,
    options: React.PropTypes.object
  },
  
  componentDidMount() {
    // create chart and do first data bind
    this._chart = new SiftChartFactory(
      this.props.type,
      this.props.data,
      this.getDOMNode(),
      this.props.options
    );
  },
  
  componentDidUpdate() {
    // update chart with new data
    this._chart.update(this.props.data);
  },
  
  componentWillUnmount() {
    // cleanup after chart
    this._chart.remove();
  },
  
  render() {
    return (
      <div className={'sift-chart ' + _.dasherize(this.props.type)}></div>
    );
  }
});

So this suggests that each chart constructor should have an update and a remove method, as well as some initialization during the instantiation within componentWillMount. Additionally, however, we can leverage a common d3 pattern to separate the initialization into a setup portion and an enter portion, where the former establishes chart dimensions and margins, and the latter can be thought of simply as an initial update. Therefore, we can write a shared (and overwritable, if need be) initialize method from within the factory where all non-data setup occurs:

// sift_chart_factory.js
var MyAwesomeScatterPlot = require('./my_awesome_scatterplot'),
    MyEvenCoolerBarGraph = require('./my_even_cooler_bar_graph');

SiftChartFactory = function(type, data, node, options) {
  var newChart;
  
  // throw an error if the chart type doesn't exist
  if (typeof SiftChartFactory[type] !== 'function' ||
      typeof SiftChartFactory[type].prototype.update !== 'function') {
    throw new Error(type + ' is not a valid Sift Chart!');
  }
  
  // copy over shared prototype methods to the child chart prototype
  if (!SiftChartFactory[type].prototype.initialize)) {
    _.extend(SiftChartFactory[type].prototype, SiftChartFactory.prototype);
  }
  
  newChart = new SiftChartFactory[type]();
  newChart.initialize(data, node, options);

  return newChart;
};

// initial d3 setup, like merging options and defaults, and setting chart dimensions,
// common for all charts. imagine we've defined a `defaults` hash of default options.
SiftChartFactory.prototype.initialize = function(data, node, opts) {
  var options = this.options = _.defaults(opts || {}, defaults);
  
  // set dimensions, translation offset for axes, etc. nothing related to data!
  // more or less taken from d3 BarChart Tutorial at http://bost.ocks.org/mike/bar/3/
  this.height = options.height - (options.margin.top + options.margin.bottom);
  this.width = chartWidth - (options.margin.right + options.margin.left);
  this.xAxis = d3.svg.axis().orient(options.xaxis.orientation);
  this.yAxis = d3.svg.axis().orient(options.yaxis.orientation);
  
  // main chart svg width, height, and margins
  this.svg = d3.select(node).append('svg')
      .attr('width', this.width + options.margin.left + options.margin.right)
      .attr('height', this.height + options.margin.top + options.margin.bottom)
    .append('g')
      .attr('transform', 'translate(' + options.margin.left + ',' + options.margin.top + ')');

  // setup axes positions only (scaling involves data and should be chart-specific)
  this.svg.append('g').attr('class', 'x axis')
      .attr('transform', 'translate(0, ' + this.height + ')');
  this.svg.append('g').attr('class', 'y axis')
      .append('text').attr('transform', 'rotate(-90)');
  
  // now make first data bind (update) via chart-specific update method
  this.update(data);
};

SiftChartFactory.MyAwesomeScatterPlot = MyAwesomeScatterPlot;
SiftChartFactory.MyEvenCoolerBarGraph = MyEvenCoolerBarGraph;

module.exports = SiftChartFactory;

Okay, so leaving remove from componentWillUnmount empty for now, this means that each custom chart constructor needs only an update method to comply with the SiftChartsFactory interface. Sweet!

// MyAwesomeScatterPlot.js, custom chart constructor example
MyAwesomeScatterPlot = function() {};

MyAwesomeScatterPlot.prototype.update = function(data) {
  // custom MyAwesomeScatterPlot update selection logic
};

Example Time!!!!!!11

Here's an example of Sift's StackedBarChart type, used here to show customers how many successful/unsuccessful requests they've made to our Events API. The StackedBarChart is simply an (empty) constructor with a custom update method (we'll leave the d3 implementation details out, since this post is much more about React integration):

chart no tooltip

To finish off this section, here is a rough example of what the parent component might look like for the chart above, simply passing the appropriate data to the <Chart /> component:

// parent component to <Chart />
module.exports = React.createClass({
  getInitialState() {
    return {
      errorsOnly: false
    }
  },
  
  onToggleErrorsOnly() {
    this.setState({
      errorsOnly: !this.state.errorsOnly
    });
  },
  
  _aggregateAppropriateData() {
    if (this.state.errorsOnly) {
      // logic to return errors only data
      return errorsOnlyData;
    }
    
    // logic to return all data
    return totalData;
  },
  
  render() {
    return (
      <h2>Events API Activity</h2>
      {/* ...other stuff... */}
      <input type='checkbox' onChange={this.onToggleErrorsOnly} value={this.state.errorsOnly} />
      <Chart
         type='StackedBarChart'
         data={this._aggregateAppropriateData()}
         options={{
           height: 400,
           width: 600
         }}
      />
    );
  }
});

The animation is left for d3 to handle—something it does very well—via its selection.transition. All we have to do in React is pass it the data! The parent component handles the state of whether to pass errors-only data or total data. So easy!!

Adding event handlers

Okay, so the previous chart example is fine and all, but let's take it a step further and provide some user interaction within the chart. Unfortunately, since we are not treating each in our bar chart as a React component, we can't take advantage of the nice abstractions React provides, i.e. <rect onClick={myClickHandler} />. Let's first figure out how we can integrate event listeners with our current setup, and then we can discuss how to implement them.

Integrating into the SiftChartFactory

Each chart type might optionally have certain hover or click interactions, etc, which should be bound as the component is mounted and unbound before the element leaves the DOM. The binding can happen as part of the factory's common initialize method, just after the initial update—easy enough. The unbinding can be part of the aforementioned empty remove method invoked in the <Chart /> component's componentWillUnmount method, which can also be refactored into a factory method! Our SiftChartFactory becomes:

// SiftChartFactory.js 
// ... 
// ... 
SiftChartFactory.prototype.initialize = function(data, node, opts) {
  // ...
  // ...

  this.update(data);
  if (typeof this._addListeners === 'function') {
    this._addListeners();
  }
};

SiftChartFactory.prototype.remove = function() {
  if (typeof this._removeListeners === 'function') { 
    this._removeListeners();
  }
};

Each chart constructor is now simply responsible for one required method (update) and two optional methods (_addListeners and _removeListeners). Now we're getting somewhere! Let's take our previous example and add a tooltip to display an individual bar's event count on hover.

Binding Mouse Events and Rendering a Tooltip

Making a tooltip is pretty tricky given our desired separation of responsibilities, because the mouse event needs to be registered in d3, but dynamically positioning and rendering a tooltip dialog is a lot easier in React. This is where our <Chart /> options prop comes in handy. Let's add a renderTooltip method to the parent component that takes in data and coordinates and returns the JSX for our desired tooltip, and then pass that method to our <Chart /> component:

// parent component to <Chart />
module.exports = React.createClass({
  getInitialState() {
    return {
      errorsOnly: false
    }
  },
  
  onToggleErrorsOnly() {
    this.setState({
      errorsOnly: !this.state.errorsOnly
    });
  },
  
  _aggregateAppropriateData() {
    if (this.state.errorsOnly) {
      // logic to return errors-only data
      return errorsOnlyData;
    }
    
    // logic to return all data
    return totalData;
  },
  
  // callback passed to Chart component
  // @param {object} intervalData - the event counts for the hovered bar group, of the shape:
  //    {
  //        x: <timestamp>,
  //        y: [
  //            {y0: <bar1-begin-height>, y1: <bar1-end-height>},
  //            {y0: <bar2-begin-height>, y1: <bar2-end-height>},
  //            ...
  //        ]
  //    }
  // @param {object} translateCoords - for positioning the tooltip correctly
  renderTooltip(intervalData, translateCoords) {
    var style = {
      transform: 'translate3d(calc(' + translateCoords.x + 'px - 50%), ' +
          translateCoords.y + 'px, 0)'
    };

    return (
      <div className='sift-chart-tooltip' style={style}>
        <p>{this._hourTooltipHelper(intervalData.x)}</p>
        {!this.state.errorsOnly ?
          <p>events {intervalData.y[1].y1 - intervalData.y[0].y0}</p>
        : null}
        <p>errors {intervalData.y[0].y1}</p>
      </div>
    );
  },
  
  render() {
    return (
      <h2>Events API Activity</h2>
      {/* ...other stuff... */}
      <input type='checkbox' onChange={this.onToggleErrorsOnly} value={this.state.errorsOnly} />
      <Chart
         type='StackedBarChart'
         data={this._aggregateAppropriateData()}
         options={{
           height: 400,
           tooltip: this.renderTooltip, // component methods are auto-bound to the component! :+1:
           width: 600
         }}
      />
    );
  }
});

Our <Chart /> component needs to be updated to include a container within which we want to render the tooltip:

// Chart.jsx
SiftChartFactory = require('./sift_chart_factory');

module.exports = React.createClass({
  // ...

  // ...

  componentWillUnmount() {
    // cleanup after chart
    this._chart.remove();
  },

  render() {
    return (
      <div classname={'sift-chart ' + _.dasherize(this.props.type)}>
        <div classname='chart-tooltip'></div>
      </div>
    );
  }
});

The awesome thing about data binding in d3 is that the data is actually bound to the DOM element. It's not stored in some jQuery DOM element wrapper abstraction; you can get the appropriate data for an element simply by calling document.querySelector(<selector>).__data__. That means that since d3 event handlers are called with the DOM element as the context, we should have everything we need in our mouseover listener.

So let's update StackedBarChart.js:

// StackedBarChart.js
StackedBarChart = function() {};

StackedBarChart.prototype.update = function(data) {
  // custom StackedBarChart update selection logic
};

StackedBarChart.prototype._addListeners = function() {
  if (this.options.tooltip) {
    // use d3 event bindings for hover
    this.svg.on('mouseover', _.partial(_onMouseOver, this.options));
    this.svg.on('mouseout', _.partial(_onMouseOut));
  }
};

StackedBarChart.prototype._removeListeners = function() {
  if (this.options.tooltip) {
    this.svg.on('mouseover', null);
    this.svg.on('mouseout', null);
  }
};

// callback in d3 is invoked with DOM element as current context!
_onMouseOver = function(options) {
  var barGroup,
      intervalData;
  
  // crawl the DOM appropriately to find our tooltip node in which
  // to render our React tooltip component.
  this.tooltipNode = this.parentNode.parentNode.children[0];
  
  // event delegation, d3-style. set one listener on the entire chart
  // as opposed to each individual `.bar` (where each <rect> in the 
  // StackedBarChart has class 'bar') , which could be 100 for a stacked bar
  // chart, 1000 if you have several stacked bar charts on a page
  if (d3.select(d3.event.target).classed('bar')) {
    // get bound data from _actual_ DOM element,
    // in this case the parent of each group of stacked bars
    barGroup = d3.event.target.parentNode;
    intervalData = barGroup.__data__;
    
    // logic to correctly position the tooltip...
    // ...
    // ...
    
    // now call the passed renderTooltip method with the data and translation coords!
    React.render(options.tooltip(intervalData, {x: transX, y: transY}), this.tooltipNode);
};

_onMouseOut = function() {
  // event delegation again
  if (d3.select(d3.event.target).classed('bar')) {
    React.unmountComponentAtNode(this.tooltipNode);
  }
};

Here's what our final version of this chart looks like:

final chart

This approach definitely blurs the lines between React/d3 responsibilities by actually calling React.render from within our d3 code. But it still feels very Reactive because our tooltip method is defined by the parent component and passed into our d3 code as a prop in the same way that we pass callbacks to other child components. Our StackedBarChart remains very reusable; another parent component with completely different data can call it with a completely different tooltip structure, and the d3 code will simply bind/unbind listeners and call the custom tooltip function with the appropriate data and positioning.

Not all interactions will be so involved. In the actual StackedBarChart used in the Sift Science Console, we also pass a click handler prop, but without rendering anything new, all we need to do is invoke it with the right data. After implementing the tooltip, that one's easy!

These are the kinds of cool projects and innovative solutions that our engineers work on every day. Interested in fighting digital fraud with the rest of the Sift Scientists? Join our team!

Introducing Score History & Integration Assessment

They’ve long been in the works, and their day has finally come. We’re excited to announce the launch of two new Sift Science features that will improve your integration and make fighting fraud easier than ever! Introducing...Score History and Integration Assessment.

Score History

The Score History feature—available to Premium and Enterprise customers—allows analysis of the events and activity that contribute to a user’s Sift Score. Want to see exactly how a user’s Sift Score has changed over time? Access their Score History in the Activity Log to determine which events led to the current Sift Score and adjust the data that you send accordingly to improve future score accuracy.

scorehistory.jpg

See how specific events change a user’s Sift Score with Score History.

Score History can help you diagnose potential integration errors that might affect Sift Science’s accuracy and ability to help your business fight fraud.

Integration Assessment

Our Integration Assessment feature is a new addition to the Integration Health section in the Developers tab. This feature reviews your Sift Science integration and notifies you of any errors detected.

If we’ve detected integration errors or issues, we’ll alert you and help you troubleshoot.

integrationerror.jpg

If your integration is error-free, we’ll let you know too!

noerror.jpg

We want to ensure that your Sift Science integration is as seamless and effective as possible. If you’re unable to diagnose your integration issues, our team is here to help. Send us a message any time at support@siftscience.com.

 

Welcome to Sift University: Ecommerce Fraud 101

Chargebacks. Card testing. Reshipping. If you’re new to the world of ecommerce fraud, these terms may seem like abstract names for even more abstract processes. Dealing with online fraud can be a frustrating experience if you don’t quite grasp how it works or where it comes from. Fraud affects multinational corporations and mom-and-pop shops alike, so learning how to protect your business is key to preventing unnecessary problems. To help you out, here’s an overview of the basics of ecommerce fraud, how it impacts your business, and how to curb its effects.

Who are we dealing with here?

There are two broad categories of fraudsters: friendly and hostile.

Friendly fraud is carried out by a customer who engages with the merchant directly, making online transactions with personal account information. In a typical scenario, the customer receives his purchased merchandise, then disputes the charge through his bank. The bank investigates the claim but usually approves the customer’s request, resulting in a chargeback. The merchant not only has to return the customer’s money, but also pays additional processing fees and penalties. It’s a tricky situation because some customers might forget that they received their purchases, but many cases are deliberate attempts to obtain merchandise or services for free.

Hostile fraud involves a third party who illegally obtains and uses someone’s account information to purchase merchandise. One way to spot a fraudster is with card testing transactions, where fraudsters test numerous stolen credit card numbers by making small purchases online. If those transaction attempts are successful, the fraudster can use the card number to make larger fraudulent purchases. Even if the merchant doesn’t accept the payment method and issues a “payment authorization failure” message, the fraudster can tweak card verification data (e.g. expiration date, CVV code) until the transaction goes through successfully. Hostile fraud can be perpetrated by anyone, from organized crime rings to individual actors with access to black markets.

What should I look out for?

Online fraud can happen in a variety of ways, and all of them can cause considerable harm to your business. Here are some common types of fraud that affect merchants:

  • Reshipping: International orders are usually more closely monitored than domestic ones, which is problematic for a fraudster who lives in a country other than the one associated with a stolen credit card. To bypass that basic rule of detection, the fraudster can recruit someone from within the cardholder’s country to receive and reship the merchandise to the final destination. Reshipping fraud can be responsible for hundreds of thousands of dollars of goods lost, and is often associated with organized crime rings.

  • Auction fraud: Auction sites and digital marketplaces have low barriers to entry, so virtually anyone can sign up and start selling merchandise. An unknowing customer might purchase goods from a fraudulent seller, who then uses stolen credit card information to buy and deliver the merchandise from a legitimate retailer. Although the customer receives his purchase in the end, the fraudster collects the sale. Once the credit card transaction is flagged as fraud by the actual cardholder, the legitimate merchant is left with no payment and lost inventory.

  • Refund abuse: Malicious customers, instead of requesting a refund from their bank, may reach out to merchants directly. These customers could include those who falsify receipts, return stolen merchandise, or make a purchase with the intent of returning it after short-term usage. A single customer might create multiple accounts to obtain fraudulent refunds, which can result in recurrent losses.

How much does fraud cost?

Losses from ecommerce fraud can be damaging not only to your bank account but also your online reputation. Billions of dollars are lost every year to fraud, hurting businesses big and small. Fraudulent chargebacks and associated fees, illegitimate refunds, and loss of inventory are just a few costs that can be avoided. Investing in the tools to detect and reduce fraud will allow you to spend more time on your business and less time dealing with fraudsters.

Don’t let fraud get in the way of your business. Start a free trial today

Best practices for building large React applications

Sift Science has been using React in production for almost a year now. In that time, we grew our application from a Backbone + React frankenstein hybrid app into one fairly large hierarchy of React components. In this post, we’ll describe some techniques and best practices that have helped us scale our UI code base with minimal friction. We’ll also walk through some common component design patterns.

Hopefully this post will save you time and sanity and provide you with some new tools for maintaining a React code base that builds on itself (instead of breaking down) as the complexity of the UI grows.

Do more in componentDidUpdate

React is all about turning the imperative task of updating the DOM into a declarative one. It turns out that declaring other types of imperative behavior as a function of props and state can be beneficial as well. Here’s an example:

Let’s say that we’re building an interface to view and edit contacts. In the screenshot to the right, “contact #3” has unsaved changes. We would like the form to automatically save if the user navigates to another contact, say Contact #2, at this point.

A reasonable way to implement this functionality would be to have a method on our main component that looks like this:

navigateToContact: function(newContactId) {
  if (this._hasUnsavedChanges()) {
    this._saveChanges();
  }
  
  this.setState({
    currentContactId: newContactId
  });
}

navigateToContact(‘contact2’)

This setup is kind of fragile though. We have to make sure that the click handlers for both the “contact #2” sidebar item and the “< prev contact” link in the bottom left corner use the navigateToContact method instead of directly setting the currentContactId state.

Here’s what it would look like if we implemented this declaratively using componentDidUpdate.

componentDidUpdate: function(prevProps, prevState) {
  if (prevState.currentContactId !== state.currentContactId) {
    if (this._hasUnsavedChanges()) {
      this._saveChanges();
    }
  }
}

In this version, the functionality to save the previous contact when navigating to a new one is baked into the component lifecycle. It’s much harder to break now since all event handlers can directly call this.setState({currentContactId: ‘contact2’}) instead of having to know to use a special method.

This example is oversimplified of course. Invoking navigateToContact in both event handlers doesn’t seem too bad here, but the problem becomes more apparent as the component grows in complexity. Declaring actions to be invoked based on prop and state changes will make your components more autonomous and reliable. This technique is especially useful for components that manage a lot of state, and it has made refactorings much more pleasant for us.

Taking composition to the max

Building a robust, maintainable, and highly composable library of components makes building your controller components much easier. In their Thinking in React tutorial, the authors recommend using the single responsibility principle as the basis for deciding what gets to be its own component.

An example of this in our code base is our Slidable component. It slides its children in and out and that’s it. Although it may seem like we’re going overboard with the single responsibility principle, it’s actually a big time saver because this thing is really good at sliding stuff. It can slide elements from any direction and use either edge as an anchor. It can use a JS transition instead of the default CSS one if that’s what the parent needs. And it’s also cross-browser compatible and unit tested. (So there’s more to the implementation than simply using CSSTransitionGroup.) Having this building block allows us to not really worry about the details of sliding stuff in components like Accordion or NotificationCenter, our Growl-like notification system. 

Splitting up our components to be more reusable makes our team more productive, enforces a consistent look and feel, and lowers the barrier of entry for people who are not on the frontend team to make UI contributions. The following sections contain tips for building components with composability in mind.

State ownership

Bump it up

Here’s another must-read section of the React docs. It advises us to keep our components as stateless as possible. If you find yourself duplicating/synchronizing state between a child and parent component, then move that state out of the child component completely. Have the parent manage state and pass it in as a prop to the child. 

Consider a Select component, a custom implementation of the HTML <select> tag. Where should the “currently selected option” state live? The Select component usually represents some kind of data in the outside world such as the value of a specific field in a model. If we make a state called selectedOption live in the Select component then we would have to update both the model and the selectedOption state when the user chooses a new option. This kind of state duplication can be avoided by making Select accept a prop called selectedOption from the parent instead of managing it’s own state.

Intuitively it makes sense that this state belongs on the model, since that’s what the component represents. The Select component itself is just a (mostly) stateless UI control and the model is the backend. Select is mostly stateless because it can actually contain one piece of state: whether or not it is currently expanded. This state can live directly on Select because it’s a UI detail and usually not something that the parent is concerned with. In the next section we will demonstrate how the isCurrentlyExpanded state can be delegated to an even lower level component to keep true to the single responsibility principle.

Separating UI details from interaction logic

We have been using the pattern of stateful higher-level components and stateless lower-level components. The stateless components provide reuse of UI rendering details, styles, and markup. The stateful wrapper components provide reuse of interaction logic. This pattern has become our single most important rule to follow for keeping our components composable. Here’s a breakdown of how we built the Select component and how it reuses UI code that’s also used for the tooltip (TooltipToggle) component.

Select

The Select component is analogous to the <select> HTML tag. It accepts props such as a list of available options and the currently selected option, but it does not own any state. Not even a state to indicate whether or not it is currently expanded. Select is composed of DropdownToggle which handles dropdown expanded/collapsed state.

DropdownToggle

This component takes in a trigger element and children that will be displayed in a dropdown HoverCard when the trigger is clicked. Select passes a button with a downward facing arrow icon as the trigger into DropdownToggle. It also passes a selectable list of options as the children into DropdownToggle.

TooltipToggle

TooltipToggle is similar in scope to DropdownToggle because it accepts a trigger component and manages state to determine whether to show its children in a HoverCard. The difference is in how it decides to show the HoverCard; the interaction logic is different. While the DropdownToggle listens for clicks on the trigger element, the TooltipToggle listens for mouse hover events. It also doesn’t close when the ESC key is pressed, but the DropdownToggle does.

HoverCard

HoverCard is the star of the show! It powers the UI markup, styles, and some of the relevant event handlers for both tooltips and dropdowns. It contains no state and it doesn’t know if it’s open or closed. If it exists, it’s open. You close it by unmounting it.

It accepts an anchor element as a prop, which is the element that the floating HoverCard positions itself around. HoverCard also has multiple looks and feels, a.k.a. flavors. One flavor is called "tooltip" which has a black background and white text color. Another is called "dropdown", which is used by the the Select component, and it has a white background and a box-shadow.

HoverCard also takes in a myriad of props for customization, such as whether or not to show a triangular caret (TooltipToggle enables this prop), or what the position of the HoverCard is relative to the anchor (TooltipToggle uses “top” while DropdownToggle uses “bottom”), and so on. HoverCard also listens for certain events (such as clicks) that happen outside of the HoverCard or key presses to the ESC key. When these events happen, HoverCard notifies the parent component via prop callbacks so that the parent can decide if it wants to close the HoverCard. One other responsibility of HoverCard is to detect whether it’s overflowing outside of the window and - if so - fix its positioning . (This functionality can be turned off via a prop).

Extracting all of the UI implementation code into HoverCard allows the higher level components like DropdownToggle and TooltipToggle to only focus on state management and interaction logic instead of re-implementing the nitty gritty, DOM positioning and styling code that is shared between all hover-y things in the UI.

This is just one example of separating UI details from interaction logic. Following this principle for all components and carefully evaluating where new pieces of state should live has really increased our ability to reuse code.

What about Flux?

Flux is great for storing application state that either doesn’t logically belong to any one specific component or state that should persist after an unmount. A common bit of advice is to never use this.state and to put everything in Flux stores -- however, this isn’t quite right. You should feel free to use this.state for component specific state that isn’t relevant after something unmounts. An example of such state is the isCurrentlyOpen state of DropdownToggle.

Flux is also quite verbose, which makes it inconvenient for data state, state that is persisted to the server. We currently use a global Backbone model cache for data fetching and saving but we’re also experimenting with a Relay-like system for REST apis. (Stay tuned for more on this topic).

For all other state, we have been able to gradually introduce Flux into our code base. It’s great because it doesn’t require a rewrite; you can use it where you want. It’s easy to unit test, easy to scale, and has provided some cool benefits like resolving circular dependencies in our core modules. It has also removed the need for hacky singleton components.

Reuse through React instead of CSS

The final scaling tip that we want to share in this post is this: ensure that React components are your primary unit of reuse. Every one of our React components has an associated CSS file. Some of our components don’t even have any JS interactions or functionality. They’re just bundles of markup and styles.

We’ve been staying away from Bootstrap-like global styles through class names. You can definitely still use Bootstrap, but wrapping the Bootstrap components in your own React components will save you time in the long run. For example, having an Icon React component that encapsulates that markup and accepts the icon name as a prop is better than having to remember exactly what markup and class names to use for icons, which in turn makes refactoring easier. It also makes it easier to add functionality to these components later on.

Although we do define a few global styles for elements such as anchors and headings, as well as many global SCSS variables, we don’t really define global css classes. Being careful to reuse UI mostly through React components has made us more productive as a team because the code is more consistent and predictable.

And that’s about it! These have been some of the guiding principles that helped us build a robust React architecture that has scaled with the size of our engineering team and the complexity of the application. Please feel free to comment with your thoughts and experiences with the ideas in this post.

Check out React Tips and Best Practices if you're looking for even more!

Taking Security Seriously

It’s been an exciting and productive start to Q2 at Sift Science. Our team has worked hard to boost your security and advance our product by ensuring that your data stays safe from cybercriminals. In addition to updating our SSL certificate -- enabling stronger encryption when our customers load the Console or send us data -- we’re proud to offer two-factor authentication! 

What is two-factor authentication?

Two-factor authentication -- also known as 2-factor auth, 2FA, or two-step verification -- is a feature that adds an additional layer of security for online accounts. In the case of Sift Science, users must opt-in in order to access this security feature that helps to verify user identity. The goal of this extra step is to combine something that you know (eg: your password) with something only you would have access to (eg: your phone).

Why is Sift offering it?

Our customers trust us with their data so we can help them fight fraud. We want to protect your accounts and data to the best of our ability. Data privacy and security are of the utmost importance and priority for us at Sift Science, and we prioritize the security of our clients’ data above all else.  Even with a strong or complex password, 2FA adds an additional layer of security to your account.

How do I set it up?

Getting started with two-factor auth on Sift Science is easy and available for you and your team right now. Just visit the Account tab to access the Your Profile section. Under Security, click the Set up two-factor authentication button to enable 2FA! 

You’ll need to install a code-generating app on your phone that you’ll use to sign into the account. The most commonly used apps are Google Authenticator and Duo Mobile.

That’s it! You’re set up with two-factor auth, and your account is even more secure.

The Sift Science team strongly recommends that you enable two-factor authentication, and encourage your team to do so as well. Think of it as that extra step that makes your account less appealing to fraudulent access.

If you have any questions about security at Sift Science, you can check out our Data & Security FAQ . We’re also available to chat at support@siftscience.com -- drop us a line!