5

what's wrong with my useEventListener ? Because each time I click on checkboxes I get this error :

useEventListener.js:14 Uncaught TypeError: callbackRef.current is not a function
    at HTMLDocument.internalCallback (useEventListener.js:14:1)

useEventListener.js

import { useEffect, useRef } from 'react'
export default function useEventListener(eventType, callback) {
    const callbackRef = useRef(callback)

    useEffect(() => {
        callbackRef.current = callback
    })    
    useEffect(() => {    
        function internalCallback(e) {
            return callbackRef.current(e)
        }    
        document.addEventListener(eventType, internalCallback);
        return () => document.removeEventListener(eventType, internalCallback)
    }, [eventType])
}

I use my hook here:

export default function useQuery(query) {
    const [queryList, setQueryList] = useState(null)
    const [isMatch, setIsMatch] = useState(false)

    useEffect(() => {
        const list = window.matchMedia(query)
        setQueryList(list)
        setIsMatch(list.matches)
    }, [query])    
    useEventListener('change', ((e) => setIsMatch(e.matches), queryList))    
    return isMatch
}
Zokulko
  • 211
  • 4
  • 25

2 Answers2

2

It seems the issue is in what you are passing to the custom hook:

useEventListener('change', ((e) => setIsMatch(e.matches), queryList))

The code here is passing a Comma Operator expression.

Comma Operator

The comma operator (,) evaluates each of its operands (from left to right) and returns the value of the last operand. This lets you create a compound expression in which multiple expressions are evaluated, with the compound expression's final value being the value of the rightmost of its member expressions. This is commonly used to provide multiple parameters to a for loop.

In other words, the expression is evaluated and the result of the final operand is returned, queryList in this case.

It's not clear what the intent was in includeing queryList, but you should remove the comma operator expression and pass just the callback function to the custom hook.

Example:

useEventListener('change', (e) => setIsMatch(e.matches));
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Hi, I've added new [issue1](https://stackoverflow.com/questions/72657845/react-why-does-only-my-first-set-of-selected-rows-turn-into-gray-before-moving-a) related to one of my previous issue and the answer that you gave me, [issue2](https://stackoverflow.com/questions/72669743/react-how-do-i-display-specific-data-with-toggle-button), [issue3](https://stackoverflow.com/questions/72672391/react-display-data-depending-on-the-value-of-a-variable-apply-and-send-apply). If you have time to look at them please :) – Zokulko Jun 18 '22 at 20:43
1

This is happening because you pass ((e) => setIsMatch(e.matches), queryList) as a second argument to useEventListener. Here's what MDS says:

The comma operator (,) evaluates each of its operands (from left to right) and returns the value of the last operand.

So your useEventListener('change', ((e) => setIsMatch(e.matches), queryList)) is equivalent to useEventListener('change', queryList). Since queryList is not a function, you get the callbackRef.current is not a function error.

UPD

I would also remove the callback ref in the useEventListener hook. It makes sense to remove an old listener and attach a new one when the callback changes. It works in the same way but is way easier to read:

export default function useEventListener(eventType, callback) { 
    useEffect(() => {    
        function internalCallback(e) {
            return callback(e);
        }    

        document.addEventListener(eventType, internalCallback);

        return () => document.removeEventListener(eventType, internalCallback);
    }, [eventType, callback]);
}