Skip to main content

Dynamic functions with fixed references

Inside a React function component, in some cases a function with a fixed reference, but a dynamic content may be needed. While this is not as straightforward with React*, it is with xoid.

*: Since this recipe was written, useEvent "the missing hook" has been added to React to solve the same problem. However ergonomicity claims of xoid still hold.

Quick Example

Let's imagine, we have the following React.useEffect. Inside it, an event listener is attached and removed every time props.number changes.

useEffect(() => {
const callback = () => console.log(props.number)
window.addEventListener('click', callback)
return () => window.removeEventListener('click', callback)
}, [props.number])

Let's assume that, due to changed app requirements, we want to attach the listener only once, and remove it once the component is unmounted. This can be achieved in the React way as the following:

// a ref to keep the value
const numberRef = useRef(props.number)
// an effect to update ref's current value when the `props.number` is changed
useEffect(() => (numberRef.current = props.number), [props.number])

// This time the `useEffect` hook has an empty dependency array, and it references the ref.
useEffect(() => {
const callback = () => console.log(numberRef.current)
window.addEventListener('click', callback)
return () => window.removeEventListener('click', callback)
}, [])

With xoid, the equivalent optimization is simply the following:

import { effect } from 'xoid'
import { useSetup } from '@xoid/react'

useSetup(($props) => {
effect(() => {
const callback = () => console.log($props.value.number)
window.addEventListener('click', callback)
return () => window.removeEventListener('click', callback)
})
}, props)

After you get used to it, xoid can feel more intuitive than React hooks in a lot of cases.

Another Example

Let's propose another problem; this time, let's examine it in a more concrete scenario.

Let's imagine, inside a React component, we're supposed to initialize a class called DragDropLibrary only once as new DragDropLibrary({ onDrop }). Let's assume we have only one chance to supply onDrop to the class instance, and this function cannot be replaced afterwards.

Imagine that props.func is our dynamic function that changes in every render, and we're supposed to feed it to onDrop.

With xoid:

useSetup(($props) => {
const onDrop = (...args) => $props.value.func(...args)
new DragDropLibrary({ onDrop })
}, props)

Think of useSetup not as a hook, but as something unchanging, some closure that does not ever rerender. @xoid/react, in some sense, is a React without hooks.

Without xoid:

const funcRef = useRef((...args) => props.func(...args))
useEffect(() => { funcRef.current = (...args) => props.func(...args) }, [props.func])
useMemo(() => {
new DragDropLibrary({ onDrop: funcRef.current })
}, [])