npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-callback-maa

v0.0.11

Published

better useCallback with helpers

Downloads

14

Readme

use-callback-maa

better than useCallback that does not change its identity, with array callback mapper to get singular callbacks to array items

Install

npm install --save use-callback-maa

Demo

See demo

Usage

Below is all the hooks provided. Personally I use useFixedCallback and useFixedCallbackMapper the most.

The binded ones I use when the handler is not in the scope of the component and thus I need to send it the latest version of some select props.

The dynamic ones are an API idea I had that avoids creating new function on every render (optimization? needs profiling). The dynamic ones are nessessarily binded since the handler is not recreated on each render thus it can not see the latest props, therfore you need to supply the props to the handler by binding them (see below example).

useFixedCallback(handler)

import React, { useState } from 'react'

import { useFixedCallback } from 'use-callback-maa'

// React.memo in conjunction with useFixedCallback will prevent <Child/> from rerendering on click
const Child = React.memo(({ onClick, label }) => {
  return <button onClick={onClick}>{label}</button>
})

const Parent = () => {
  const [counter, setCounter] = useState(0)
  const onClick = useFixedCallback((e: React.MouseEvent) => {
    e.stopPropagation()
    setCounter(counter + 1)
  })
  return (
    <div>
      <div>Counter: {counter}</div>
      <Child onClick={onClick} label='Click Me' />
    </div>
  )
}

const App = () => {
  return <Parent />
}

useFixedCallbackMapper(handler, keyGetter: string | KeyGetter)

This method creates a mapper function that can be then used to map an array and provide each item in that array with a fixed identity callback. Note each item's identity is obtained using the keyGetter (which is kindof like lodash's iteratee)

Note the keyGetter is only set once on first render so please keep it constant throughout

import React, { useState } from 'react'

import { useFixedCallbackMapper } from 'use-callback-maa'

// React.memo in conjunction with useFixedCallbackMapper will prevent <Child/> from rerendering on click
const Child = React.memo(({ onClick, label }) => {
  const renderCountRef = useRef(0)
  ++renderCountRef.current
  return (
    <button onClick={onClick}>
      {label}
      <br />
      RenderCount:{renderCountRef.current}
    </button>
  )
})

const Parent = () => {
  const [counter, setCounter] = useState(0)
  const [items, setItems] = useState(() => [
    { id: '1', label: 'Item 1' },
    { id: '2', label: 'Item 2' },
    { id: '3', label: 'Item 3' }
  ])

  const mapper = useFixedCallbackMapper((item, e) => {
    e.stopPropagation()
    setCounter(counter + 1)
    if (counter % 6 === 1) {
      setItems(
        items.map((itm) => {
          return itm.id === item.id
            ? {
                ...itm,
                label: `${itm.label.split('[')[0]}[Updated@${counter + 1}]`
              }
            : itm
        })
      )
    }
    if (counter % 6 === 3) {
      setItems([
        ...items,
        {
          id: `${parseFloat(items[items.length - 1].id) + 1}`,
          label: `Item ${parseFloat(items[items.length - 1].id) + 1}[NEW]`
        }
      ])
    }
    if (counter % 6 === 5) {
      setItems(items.filter(({ id }) => id !== item.id))
    }
  }, 'id')

  return (
    <div>
      <b>Every second click will do something</b>
      <div>Counter: {counter}</div>
      {mapper(items, (key, callback, item, i, items) => (
        <Child key={key} onClick={callback} label={item.label} />
      ))}
    </div>
  )
}

const App = () => {
  return <Parent />
}

useBindedCallback(handler, ...bindArgs)

import React, { useState } from 'react'

import { useBindedCallback } from 'use-callback-maa'

// React.memo in conjunction with useBindedCallback will prevent <Child/> from rerendering on click
const Child = React.memo(({ onClick, label }) => {
  return <button onClick={onClick}>{label}</button>
})

const counterSetter = (setCounter, counter) => setCounter(counter + 1)
const Parent = () => {
  const [counter, setCounter] = useState(0)

  // This way the handler will not be recreated on each render
  const onClick = useBindedCallback(counterSetter, setCounter, counter)

  // const onClick = useBindedCallback(
  //   (setCounter, counter, e) => {
  //     e.stopPropagation()
  //     setCounter(counter + 1)
  //   },
  //   setCounter,
  //   counter
  // )

  return (
    <div>
      <div>Counter: {counter}</div>
      <Child onClick={onClick} label='Click Me' />
    </div>
  )
}

const App = () => {
  return <Parent />
}

useDynamicBindedCallback(...bindArgs)

The hooks with dynamic in their name use a new API to avoid re creating the handler on every render. But since the handler is created once the dependencies must be binded (using bindArgs).

import React, { useState } from 'react'

import { useDynamicBindedCallback } from 'use-callback-maa'

// React.memo in conjunction with useDynamicBindedCallback will prevent <Child/> from rerendering on click
const Child = React.memo(({ onClick, label }) => {
  return <button onClick={onClick}>{label}</button>
})

const Parent = () => {
  const [counter, setCounter] = useState(0)
  const onClick = useDynamicBindedCallback(setCounter, counter)
  onClick.setHandler?.((setCounter, counter, e) => {
    e.stopPropagation()
    setCounter(counter + 1)
  })
  // setHandler will set the callback handler and then remove itself from the callback
  // so come the second render setHandler will be undefined. Hence optional chaining will
  // not call it and a new handler will not be needlessly created

  return (
    <div>
      <div>
        Counter: {counter}
        {onClick.setHandler ? 'WITH setHandler???' : ''}
      </div>
      <Child onClick={onClick} label='Click Me' />
    </div>
  )
}

const App = () => {
  return <Parent />
}

useBindedCallbackMapper(handler, keyGetter: string | KeyGetter, ...bindArgs)

Note the keyGetter is only set once on first render so please keep it constant throughout

import React, { useState } from 'react'

import { useBindedCallbackMapper } from 'use-callback-maa'

// React.memo in conjunction with useBindedCallbackMapper will prevent <Child/> from rerendering on click
const Child = React.memo(({ onClick, label }) => {
  const renderCountRef = useRef(0)
  ++renderCountRef.current
  return (
    <button onClick={onClick}>
      {label}
      <br />
      RenderCount:{renderCountRef.current}
    </button>
  )
})

const Parent = () => {
  const [counter, setCounter] = useState(0)
  const [items, setItems] = useState(() => [
    { id: '1', label: 'Item 1' },
    { id: '2', label: 'Item 2' },
    { id: '3', label: 'Item 3' }
  ])

  const mapper = useBindedCallbackMapper(
    (item, /*, binded, args, will, show, here, */ e) => {
      e.stopPropagation()
      setCounter(counter + 1)
      if (counter % 6 === 1) {
        setItems(
          items.map((itm) => {
            return itm.id === item.id
              ? {
                  ...itm,
                  label: `${itm.label.split('[')[0]}[Updated@${counter + 1}]`
                }
              : itm
          })
        )
      }
      if (counter % 6 === 3) {
        setItems([
          ...items,
          {
            id: `${parseFloat(items[items.length - 1].id) + 1}`,
            label: `Item ${parseFloat(items[items.length - 1].id) + 1}[NEW]`
          }
        ])
      }
      if (counter % 6 === 5) {
        setItems(items.filter(({ id }) => id !== item.id))
      }
    },
    'id' /*, binded, args, comes, here */
  )

  return (
    <div>
      <b>Every second click will do something</b>
      <div>Counter: {counter}</div>
      {mapper(items, (key, callback, item, i, items) => (
        <Child key={key} onClick={callback} label={item.label} />
      ))}
    </div>
  )
}

const App = () => {
  return <Parent />
}

useDynamicBindedCallbackMapper(...bindArgs)

import React, { useState } from 'react'

import { useDynamicBindedCallbackMapper } from 'use-callback-maa'

// React.memo in conjunction with useDynamicBindedCallbackMapper will prevent <Child/> from rerendering on click
const Child = React.memo(({ onClick, label }) => {
  const renderCountRef = useRef(0)
  ++renderCountRef.current
  return (
    <button onClick={onClick}>
      {label}
      <br />
      RenderCount:{renderCountRef.current}
    </button>
  )
})

const Parent = () => {
  const [counter, setCounter] = useState(0)
  const [items, setItems] = useState(() => [
    { id: '1', label: 'Item 1' },
    { id: '2', label: 'Item 2' },
    { id: '3', label: 'Item 3' }
  ])

  const mapper = useDynamicBindedCallbackMapper(counter, items) // setCounter and setItems have fixed identities no need to bind them
  mapper.setKeyGetter?.('id')
  mapper.setHandler?.((item, counter, items, e) => {
    e.stopPropagation()
    setCounter(counter + 1)
    if (counter % 6 === 1) {
      setItems(
        items.map((itm) => {
          return itm.id === item.id
            ? {
                ...itm,
                label: `${itm.label.split('[')[0]}[Updated@${counter + 1}]`
              }
            : itm
        })
      )
    }
    if (counter % 6 === 3) {
      setItems([
        ...items,
        {
          id: `${parseFloat(items[items.length - 1].id) + 1}`,
          label: `Item ${parseFloat(items[items.length - 1].id) + 1}[NEW]`
        }
      ])
    }
    if (counter % 6 === 5) {
      setItems(items.filter(({ id }) => id !== item.id))
    }
  })

  return (
    <div>
      <b>Every second click will do something</b>
      <div>Counter: {counter}</div>
      {mapper(items, (key, callback, item, i, items) => (
        <Child key={key} onClick={callback} label={item.label} />
      ))}
    </div>
  )
}

const App = () => {
  return <Parent />
}

License

MIT © maa105