use-callback-maa
v0.0.11
Published
better useCallback with helpers
Downloads
14
Maintainers
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
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