Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
reflective-bind
Advanced tools
Eliminate wasteful re-rendering in React components caused by inline functions
With reflective-bind, you can freely use inline functions in render without worrying about wasteful re-rendering of React pure components. It works for both stateless functional components and class components. The best part is, it requires almost no code change 🙌
Check out our blog post for more info on the motivation and the inner workings of reflective-bind.
For one of Flexport's more complex forms, turning on the Babel transform reduced the wasted render time from 175ms to 18ms. Your mileage will vary based on the structure of your app and your use of pure components.
npm install --save reflective-bind
NOTE: the design goal of the plugin is to preserve the semantics of your code. Your inline functions will still create new function instances each render. The transform simply enables the equality comparison of two function instances via reflection.
Add it to the top of your plugin list in .babelrc
(it must be run before other plugins that transform arrow functions and bind
calls):
"plugins": [
["reflective-bind/babel", {"log": "debug"}],
...
]
And call reflective bind’s shouldComponentUpdate
helper function in your component:
import {shouldComponentUpdate} from "reflective-bind";
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return shouldComponentUpdate(this, nextProps, nextState);
}
...
}
Alternatively, subclass React.Component
and override shouldComponentUpdate
. Then extend your custom component when you want a pure component.
import * as React from "react";
import {shouldComponentUpdate} from "reflective-bind";
export default class CustomPureComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return shouldComponentUpdate(this, nextProps, nextState);
}
}
If you do not want the babel plugin to process a specific file, add the following line to your file:
// @no-reflective-bind-babel
When specified, only transform props whose name matches the regular expression. The intended use case is to avoid transforming render callbacks, as this can lead to stale render bugs.
For example, if all of your non-render callbacks are prefixed with on
, such as onClick
, consider using "propRegex": "^on[A-Z].*$"
.
Specifies the minimum level of logs to output to the console. Enabling logging at a given level also enables logging at all higher levels.
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.
The plugin simply transforms inline functions into calls to reflectiveBind
. This then allows the shouldComponentUpdate
helper function to use reflectiveEqual
in the shallow comparison equality check.
Binding your function with reflectiveBind
simply stores the original function, the context (thisArg), and the arguments as properties 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.
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);
The following are examples of some inline functions that will be transformed into calls to reflectiveBind
by the babel plugin:
function MyComponent(props) {
const msg = "Hello " + props.user.name.first;
return <PureChild onClick={() => alert(msg)} />;
}
Function.prototype.bind
:function MyComponent(props) {
const handleClick = props.callback.bind(undefined, "yay");
return <PureChild onClick={handleClick} />;
}
function MyComponent(props) {
let handleClick = () => {...};
if (...) {
handleClick = () => {...};
} else if (...) {
handleClick = () => {...};
}
return <PureChild onClick={handleClick} />
}
function MyComponent(props) {
const handleClick = props.condition
? () => {...}
: () => {...};
return <PureChild onClick={handleClick} />
}
function MyComponent(props) {
// PureChild will re-render whenever `props` changes (bad)
const badHandleClick = () => alert(props.user.name.first);
const firstName = props.user.name.first;
// Now, PureChild will only re-render when firstName changes (good)
const goodHandleClick = () => alert(firstName);
return (
<div>
<PureChild onClick={badHandleClick} />
<PureChild onClick={goodHandleClick} />
</div>
);
}
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.
function MyComponent(props) {
let foo = 1;
const badHandleClick = () => {
// Referencing `foo`, which is reassigned after this arrow function, will
// prevent this arrow function from being transformed.
alert(foo);
};
foo = 2;
return <PureChild onClick={badHandleClick} />;
}
function MyComponent(props) {
// This arrow function won't be transformed because `fn` is not referenced
// directly in the JSX.
const fn = () => {...};
const badHandleClick = fn;
// This arrow function will be transformed since `goodHandleClick` is
// referenced directly 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>
);
}
FAQs
Eliminate wasteful re-rendering in React components caused by inline functions
The npm package reflective-bind receives a total of 1,523 weekly downloads. As such, reflective-bind popularity was classified as popular.
We found that reflective-bind demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
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.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.