@incoqnito.io/smart-component

1.3.0 • Public • Published

smart-component

A HOC enabling you to easily implement extended property equality checks for your React Components.

Motivation

The Problem

When writing components whose performance is crucial (for example top level components or components you render A LOT [something among the lines of > 700 times]) you will often want them to only update, when their visual appearance will change.

How do you do that? Right! Extending from React.PureComponent - but wait! My component is still rerendering, even though the data it receives doesn't change?!

This can often be caused by passing objects to your component - be it a JS Date(), an array of entities to render or even the styles property. Why?

React.PureComponent only does a swallow equality check. This means it does a comparison using the === operator on every key/value pair of the old properties and compares it to the value in the new properties. However === on objects (arrays are also objects in this manner) will only check wether or not they reference to the same chunk of memory - not if their content is the same.

Don't believe me? Just try { key: true } === { key: true } in your console. It'll actually return false! And that's exactly what happens, if you use a PureComponent like that:

<MyPureComponent style={{ width: '100%' }} />

It rerenders every time its parent rerenders (or more often), because on every parent render a new object is beeing created (with the same content or key/value pairs but pointing at a different chunk of memory, so oldProps.style === newProps.style will return false) - resulting in your PureComponent thinking it received changed props every time its parent rerenders.

The Cure

Implementing your own (shouldComponentUpdate(nextProps))[https://reactjs.org/docs/react-component.html#shouldcomponentupdate] every time!

But this can be quiet time consuming, as you will have to handle every single prop which might change and cause the need to rerender - who likes writing countless lines of if (this.props.key !== nextProps.key) { return true } (well in addition to the props where you need to do deep equality checks or something equal)? Of course you can automate that (reducing over the entries of your props), but than you are in the need of ugly switch (...) { case ... } for every prop you want to treat differently...

That's why smart-component is there for you!

Just give it equality checks for the props you want to treat special (in a very clean and verbose way) and let it handle the rest using good old ===!

Installation

npm install --save @icoqnito.io/smart-component

Usage

index.js

import React from 'react'
import {
  DateDisplay,
  SmartDateDisplay
}

export default class extends React.PureComponent {

  state = {
    date: new Date()
  }

  // this function generates a copy of the current value of this.state.date and sets the state to this copy
  // -> that means that the new value of this.state.date describes the same point in time as the old one
  // however (because new Date() instantiates a new JS Date() object) date === new Date(date) will return false
  // causing a PureComponent to rerender as it only checks using the === operator
  resetDate = () => {
    this.setState({
      date: new Date(this.state.date)
    })
  }

  onStandardRender = () => {
    this.setState((prevState) => ({ standardRenderedCount: prevState.standardRenderedCount + 1 }))
  }

  onSmartRender = () => {
    this.setState((prevState) => ({ smartRenderedCount: prevState.smartRenderedCount + 1 }))
  }

  render () {
    return (
      <div>
        <span>Standard Component rendered {this.state.standardRenderedCount} times<span>
        <DateDisplay date={this.state.date} onRender={this.onStandardRender} />

        <div> &nbsp </div>

        <span>Smart Component rendered {this.state.smartRenderedCount} times<span>
        <SmartDateDisplay date={this.state.date} onRender={this.onSmartRender} />

        <div> &nbsp </div>

        <button type='button' onClick={this.resetDate}>date = new Date(date)</button>
      </div>
    )
  }

}

DateDisplay.js

import React from 'react'
import smartComponent from 'smart-component'

export class DateDisplay extends React.PureComponent {

  render () {
    this.props.onRender && this.props.onRender()

    return (
      <div>{ props.date }</div>
    )
  }

}

export const SmartDateDisplay = smartComponent({
  date: (a, b) => a.getTime() === b.getTime()   // comparison function to compare the values of props.date at two different points in time; true -> equal
  // getTime() returns the timestamp as a number, making you able to compare them for equality just like normal numbers
}, DateDisplay)

You will see the standard component calling its onRender every time you click the button - opposing to the Smart Component, which uses its custom equality check implemented by you to prevent useless rerenders.

Package Sidebar

Install

npm i @incoqnito.io/smart-component

Weekly Downloads

1

Version

1.3.0

License

MIT

Unpacked Size

12.3 kB

Total Files

4

Last publish

Collaborators

  • dragonraider5
  • incoqnito
  • kodekid
  • misago