• Stars
    star
    163
  • Rank 222,783 (Top 5 %)
  • Language
    TypeScript
  • Created over 4 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

⚛️ React hook for memoizing values inline anywhere in a component

⚛︎ use-inline-memo

React hook for memoizing values and callbacks anywhere in a component.

Build status npm version


Like other hooks, you can call React.useMemo() and React.useCallback() only at the top of your component function and not use them conditionally.

Inline memos let us memoize anywhere without the restrictions that apply to the usage of hooks!

import { Button, TextField } from "@material-ui/core"
import React from "react"
import useInlineMemo from "use-inline-memo"

function NameForm(props) {
  const memo = useInlineMemo()
  const [newName, setNewName] = React.useState(props.prevName)

  // Conditional return prevents calling any hook after this line
  if (props.disabled) {
    return <div>(Disabled)</div>
  }

  return (
    <React.Fragment>
      <TextField
        label="Name"
        onChange={memo.nameChange(event => setNewName(event.target.value), [])}
        value={newName}
      />
      <Button
        onClick={memo.submitClick(() => props.onSubmit(newName), [newName])}
        style={memo.submitStyle({ margin: "16px auto 0" }, [])}
      >
        Submit
      </Button>
    </React.Fragment>
  )
}

Installation

npm install use-inline-memo

Usage

Everytime you want to memoize a value, call memo.X() with an identifier X of your choice. This identifier will be used to map the current call to values memoized in previous component renderings.

Calling useInlineMemo() without arguments should work in all ES2015+ runtimes as it requires the ES2015 Proxy. If you need to support older browsers like the Internet Explorer, you will have to state the memoization keys up-front:

function NameForm(props) {
  // Explicit keys to make it work in IE11
  const memo = useInlineMemo("nameChange", "submitClick", "submitStyle")
  const [newName, setNewName] = React.useState(props.prevName)

  return (
    <React.Fragment>
      <TextField
        label="Name"
        onChange={memo.nameChange(event => setNewName(event.target.value), [])}
        value={newName}
      />
      <Button
        onClick={memo.submitClick(() => props.onSubmit(newName), [newName])}
        style={memo.submitStyle({ margin: "16px auto 0" }, [])}
      >
        Submit
      </Button>
    </React.Fragment>
  )
}

When not run in production, there is also a check in place to prevent you from accidentally using the same identifier for two different values. Calling memo.X() with the same X twice during one rendering will lead to an error.

Use cases

Event listeners

Inline memoizers are perfect to define simple one-line callbacks in-place in the JSX without sacrificing performance.

// Before
function Component() {
  const [value, setValue] = React.useState("")
  const onUserInput = React.useCallback(
    (event: React.SyntheticEvent) => setValue(event.target.value),
    []
  )
  return (
    <input onChange={onUserInput} value={value} />
  )
}
// After
function Component() {
  const memo = useInlineMemo()
  const [value, setValue] = React.useState("")
  return (
    <input
      onChange={memo.textChange(event => setValue(event.target.value), [])}
      value={value}
    />
  )
}

style props & other objects

Using inline style props is oftentimes an express ticket to unnecessary re-renderings as you will create a new style object on each rendering, even though its content will in many cases never change.

// Before
function Component() {
  return (
    <Button style={{ color: "red" }} type="submit">
      Delete
    </Button>
  )
}
// After
function Component() {
  const memo = useInlineMemo()
  return (
    <Button style={memo.buttonStyle({ color: "red" }, [])} type="submit">
      Delete
    </Button>
  )
}

You don't need to memoize every style object of every single DOM element, though. Use it whenever you pass an object to a complex component which is expensive to re-render.

For more background information check out FAQs: Why memoize objects?.

API

useInlineMemo(): MemoFunction

Call it once in a component to obtain the memo object carrying the memoization functions.

useInlineMemo(...keys: string[]): MemoFunction

State the memoization keys explicitly if you need to support Internet Explorer where Proxy is not available.

memo[id: string](value: T, deps: any[]): T

Works the same as a call to React.useMemo() or React.useCallback(), only without the wrapping callback function. That wrapping function is useful to run expensive instantiations only if we actually refresh the value, but for our use case this is rather unlikely, so we provide you with a more convenient API instead.

id is used to map different memoization calls between component renderings.

FAQs

How does that work?

The reason why React hooks cannot be called arbitrarily is that React needs to match the current hook call to previous calls. The only way it can match them is by assuming that the same hooks will always be called in the same order.

So what we do here is to provide a hook useInlineMemo() that creates a Map to match memo.X() calls to the memoized value and the deps array. We can match calls to memo.X() between different re-renderings by using X as an identifier.

Why is memoization so important?

To ensure good performance you want to re-render as few components as possible if some application state changes. In React we use React.memo() for that which judges by comparing the current component props to the props of the previous rendering.

Without memoization we will very often create the same objects, callbacks, ... with the same content over and over again for each rendering, but as they are new instances every time, they will not be recognized as the same values and cause unnecessary re-renderings (see "Why memoize objects?" below).

Why memoize objects?

When React.memo() determines whether a component needs to be re-rendered, it tests if the property values are equivalent to the property values of the last rendering. It does though by comparing them for equality by reference, as anything else would take too much time.

Now if the parent component creates a new object and passes it to the component as a property, React will only check if the object is exactly the same object instance as for the last rendering (newObject === prevObject). Even if the object has exactly the same properties as before, with the exact same values, it will nevertheless be a new object that just happens to have the same content.

Without memoization React.memo() will always re-render the component as we keep passing new object instances – the equality comparison of the old and the new property value will never be true. Memoization makes sure to re-use the actual last object instance, thus skipping re-rendering.

License

MIT

More Repositories

1

webpack-blocks

📦 Configure webpack using functional feature blocks.
JavaScript
2,979
star
2

threads.js

🧵 Make web workers & worker threads as simple as a function call.
TypeScript
2,942
star
3

leakage

🐛 Memory leak testing for node.
JavaScript
1,583
star
4

pg-listen

📡 PostgreSQL LISTEN & NOTIFY for node.js that finally works.
TypeScript
540
star
5

typed-emitter

🔩 Type-safe event emitter interface for TypeScript
JavaScript
250
star
6

postguard

🐛 Statically validate Postgres SQL queries in JS / TS code and derive schemas.
TypeScript
161
star
7

laravel-js-localization

Simple, ease-to-use and flexible package for the Laravel web framework. Allows you to use localized messages of the Laravel webapp (see `resources/lang` directory) in your Javascript code.
PHP
143
star
8

squid

🦑 Provides SQL tagged template strings and schema definition functions.
TypeScript
124
star
9

ava-ts

🚀 Fork of the AVA test runner with native typescript support
JavaScript
116
star
10

postcss-debug

Debug your postcss workflow with ease! Creates snapshots of your CSS files before/after each postcss plugin is run.
JavaScript
94
star
11

react-usestyles

🖍 Style components using React hooks. Abstracts the styling library away.
JavaScript
87
star
12

postcss-theme

PostCSS plugin to enable versatile theming.
JavaScript
87
star
13

react-stateful-fn

⚛ Stateful functional components for React.
JavaScript
57
star
14

puppet-run

🤖 Run anything JavaScript in a headless Chrome from your command line
TypeScript
53
star
15

threadpool-js

Javascript thread pool implementation using web workers.
JavaScript
47
star
16

jquery-dim-background

jQuery plugin to dim the current page except for some user-defined top elements.
JavaScript
43
star
17

observable-fns

🕵️‍♀️ Light-weight observable implementation and functional utilities in TypeScript
TypeScript
41
star
18

npm-launch

🚀 Minimalistic task runner on steroids!
JavaScript
39
star
19

drag-mock

Trigger HTML5 drag & drop events for testing
JavaScript
35
star
20

gear

🛠 Experimental tool to bootstrap typed JavaScript code.
JavaScript
33
star
21

proposal-double-colon-types

🤓 JS / Flow syntax proposal. Types à la Hindley-Milner.
20
star
22

http-event-stream

📡 Modern spec-compliant Server Sent Events stream implementation.
TypeScript
19
star
23

ts

⚙️ The CLI that TypeScript deserves.
TypeScript
18
star
24

key-store

🔐 Isomorphic encrypted key store written in TypeScript.
TypeScript
17
star
25

plow

👨‍🌾 Postgres migrations and seeding made easy
TypeScript
14
star
26

isomorphic-crypto

🔒 Isomorphic crypto package for node and the browser.
JavaScript
12
star
27

type-reflect

☝️ TypeScript plugin providing access to type information at runtime
TypeScript
11
star
28

srv

📡 Functional node server. Composable routing. Take a request, return a response.
TypeScript
9
star
29

rungpt

GPT client with local plugin framework, built by GPT-4
TypeScript
9
star
30

php-easygit

Manage Git repositories from within your PHP webapp. Commit, branch, clone, checkout, ...
PHP
7
star
31

zaster

💸 Headless multi-blockchain wallet and SDK.
TypeScript
7
star
32

react-commandments

📖 Thou shalt honor thy reactive code and keep it holy.
6
star
33

koa-router-index

Koa v2 middleware to create an index page for API servers.
JavaScript
5
star
34

gulp-elixir-modules

Elixir extension for handling frontend modules easily.
JavaScript
5
star
35

json-sql-import

Small PHP tool to import JSON data into database tables using transformation rules.
PHP
4
star
36

puppet-run-plugins

🧩 Plugins for puppet-run.
TypeScript
4
star
37

shutter-legacy

📸 Visual snapshot testing with no effort.
TypeScript
3
star
38

deep-assert

🔍 Better deep-equals comparison, supporting custom property assertions and pretty diffs.
TypeScript
3
star
39

ideabox

Place to collect techy ideas and get feedback.
1
star
40

bundle-decomposition-research

JavaScript
1
star
41

stellar-wallet

With the new Stellar wallet to the moon 🚀
JavaScript
1
star