Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

reflective-bind

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

reflective-bind

Eliminate wasteful re-rendering in React components caused by inline functions

  • 0.0.2
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
4.3K
decreased by-7.3%
Maintainers
1
Weekly downloads
 
Created
Source

Build Status codecov

Reflective Bind

The reflective-bind/babel plugin enables you freely use inline arrow functions in the render method of React components without worrying about deoptimizing pure components.

Motivation

Using inline functions (arrow functions and Function.prototype.bind) in render will deoptimize pure child components. As a result, many React developers encourage you to never use inline functions in your render function. However, others think that avoiding them is premature optimization. With reflective bind you can use inline functions and have optimized pure components.

Installation

npm install --save reflective-bind

Using the babel plugin

Add it to the top of your plugin list in .babelrc (just has to come before other plugins that transform arrow functions and bind calls):

"plugins": [
  "reflective-bind/babel",
  ...
]

And implement shouldComponentUpdate in your component:

import {shouldComponentUpdate} from "reflective-bind";

class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shouldComponentUpdate(this, nextProps, nextState);
  }
  ...
}

If you’re already using React.PureComponent and want to avoid updating all of your components, consider monkey patching shouldComponentUpdate 🙊

import React from "react";
import {shouldComponentUpdate} from "reflective-bind";

React.PureComponent.prototype.shouldComponentUpdate = function(
  nextProps,
  nextState
) {
  return shouldComponentUpdate(this, nextProps, nextState);
};

If for some reason you want the babel plugin to skip processing a specific file, add the following to the file.

// @no-reflective-bind-babel

Dependencies

The babel plugin will add ES6 import declarations to your code. This shouldn’t be an issue if you’re using using babel-preset-env or babel-preset-es2015, but just make sure that some plugin/preset can transform the import declarations to your needs.

What the plugin does

The plugin simply transforms inline functions into calls to reflectiveBind, and the shouldComponentUpdate helper function uses reflectiveEqual in the shallow comparison equality check.

Using reflectiveBind manually

Binding your function with reflectiveBind simply stores the original function, the context (thisArg), and the args on the bound function instance. This allows you to check if two reflectively bound functions are equal.

import reflectiveBind, {reflectiveEqual} from "reflective-bind";

function baseFn(msg) {
  alert(msg);
}

const fn1 = reflectiveBind(baseFn, undefined, "hello");
const fn2 = reflectiveBind(baseFn, undefined, "hello");

fn1 === fn2 // false
reflectiveEqual(fn1, fn2) // true

const fn3 = reflectiveBind(baseFn, undefined, "world");
reflectiveEqual(fn1, fn3) // false

Note that reflectiveEqual only works for reflectively bound functions.

reflectiveEqual(1, 1) // false
reflectiveEqual(baseFn, baseFn) // false

We also expose a isReflective helper function that lets you check if something is a reflectively bound function.

Flow types

All exported functions are flow typed out of the box. reflectiveBind is typed with function overloading:

// Function with 0 args
declare function reflectiveBind<A>(f: () => A, ctx: mixed): () => A;

// Function with 1 arg
declare function reflectiveBind<A, B>(f: (A) => B, ctx: mixed): A => B;

declare function reflectiveBind<A, B>(f: (A) => B, ctx: mixed, a: A): () => B;

...

We currently support reflectiveBind calls up to 4 args:

reflectiveBind(baseFn, ctx, a, b, c, d);

Babel plugin examples

The following examples of inline functions can all be transformed into calls to reflectiveBind:

function MyComponent(props) {
  const msg = "Hello " + props.user.name.first;
  return <PureChild onClick={() => alert(msg)} />
}
function MyComponent(props) {
  // Supports Function.prototype.bind
  const handleClick = props.callback.bind(undefined, "yay");
  return <PureChild onClick={handleClick} />
}
function MyComponent(props) {
  // Supports multiple assignments / reassignments
  let handleClick = () => {...};
  
  if (...) {
      handleClick = () => {...};
  } else if (...) {
      handleClick = () => {...};
  }

  return <PureChild onClick={handleClick} />
}
function MyComponent(props) {
  // Supports ternary expressions
  const handleClick = props.condition
    ? () => {...}
    : () => {...};

  return <PureChild onClick={handleClick} />
}
class MyComponent extends React.Component {
  render() {
    // For class components, referencing `this.props.___` and `this.state.___`
    // from within your arrow function is supported, but we recommend you to
    // extract these references out to a const, especially if you are
    // accessing deeply nested attributes (e.g. `this.props.user.name.first`).
    
    // PureChild will re-render whenever `user` changes.
    const decentHandler = () => alert(this.props.user.name.first);
    
    // PureChild re-render ONLY when the first name changes.
    const firstName = this.props.user.name.first;
    const betterHandler = () => alert(firstName);
    
    return (
      <div>
        <PureChild onClick={decentHandler} />
        <PureChild onClick={betterHandler} />
      </div>
    );
  }
}

Unsupported cases

There are a few edge cases that can cause an arrow function to not be transformed. Nothing breaks, you just won’t have optimized code.

  • Your arrow function should not close over variables whose value is set after the arrow function.
function MyComponent(props) {
  let foo = 1;
  
  const badHandleClick = () => {
    // Referencing `foo` will deopt since it is reassigned after
    // this arrow function.
    alert(foo);
  };
  
  foo = 2;

  return <PureChild onClick={badHandleClick} />
}
  • Your arrow function must be defined inline the JSX, or at most 1 reference away.
function MyComponent(props) {
  // This arrow function won't be optimized because `fn` is not referenced
  // in the JSX.
  const fn = () => {...};
  const badHandleClick = fn;
                    
  // This will be optimized since `goodHandleClick` is referenced in the JSX.
  const goodHandleClick = () => {...};
                    
  return (
    <div>
      <PureChild onClick={badHandleClick} />
      
      <PureChild onClick={goodHandleClick} />
      
      {/* This will be optimized since it is defined directly in the JSX */}
      <PureChild onClick={() => {...}} />
    </div>
  );
}
  • For maximum optimization, avoid accessing nested attributes in your arrow function. Prefer to pull the values out to a const and close over it in your arrow function.
function MyComponent(props) {
  
  const badHandleClick = () => {
    // Referencing nested attributes inside the arrow function will cause
    // PureChild to re-render whenever the outermost object changes. In this
    // case, `props` will change every render, which will cause PureChild to
    // always re-render.
    alert(props.user.name.first);
  };
  
  const firstName = props.user.name.first;
  const goodHandleClick = () => {
    // To avoid referencing nested attributes inside the arrow function,
    // simply extract it out to a const, and reference the const.
    alert(firstName);
  };
  
  return (
    <div>
      <PureChild onClick={badHandleClick} />
      <PureChild onClick={goodHandleClick} />
    </div>
  );
}

Keywords

FAQs

Package last updated on 20 Oct 2017

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc