reactjsreact-hooks

How to collect and return multiple functions from a custom hook


Main.tsx:

import React from "react";
import { useRectangleControls } from "src/useRectangleControls";

export function Main() {
    const rectangleRef = React.useRef<HTMLDivElement>(null);
    const mainControls = useRectangleControls(rectangleRef);

    React.useEffect(() => {
        console.log(mainControls.isMouseInRectangle);
    }, [mainControls.isMouseInRectangle]);

    return (
        <div className="w-full h-screen flex items-center justify-center">
            <div className="w-[60%] h-[30%] bg-green-900" ref={rectangleRef} onMouseEnter={mainControls.onMouseEnter} onMouseLeave={mainControls.onMouseLeave}></div>
        </div>
    );
}

The above component returns a rectangle. The color of the rectangle depends on the mouse position. The event handlers for onMouseEnter and onMouseLeave were shifted to a custom hook called useRectangleControls:

import React from "react";

export function useRectangleControls(rectangleRef: React.RefObject<HTMLDivElement>) {
    const [isMouseInRectangle, setIsMouseInRectangle] = React.useState(false);

    function onMouseEnter() {
        if (rectangleRef.current) {
            setIsMouseInRectangle(true);
            rectangleRef.current.classList.remove("bg-green-900");
            rectangleRef.current.classList.add("bg-red-900");
        }
    }
    function onMouseLeave() {
        if (rectangleRef.current) {
            setIsMouseInRectangle(false);
            rectangleRef.current.classList.remove("bg-red-900");
            rectangleRef.current.classList.add("bg-green-900");
        }
    }


    return { onMouseEnter, onMouseLeave, isMouseInRectangle };
}

I want to collect all functions and return them in one variable. I want the return statement to look something like this: return {rectangleControls, isMouseInRectangle} where rectangleControls contains all functions inside the useRectangleControls custom hook.

My goal is to avoid updating the return statement each time I add a new function to useRectangleControls


Solution

  • You can do that in the following way

    import React from "react";
    
    export interface Indexable<T = any> {
        [key: string]: T|undefined;
    }
    
    export interface RectangleControls {
        onMouseEnter: () => void;
        onMouseLeave: () => void;
    }
    
    export function useRectangleControls(rectangleRef: React.RefObject<HTMLDivElement>) {
        const [isMouseInRectangle, setIsMouseInRectangle] = React.useState(false);
        // const rectangleControls: Indexable = {}; // one approach for typescript
        const rectangleControls: RectangleControls = {
            onMouseEnter: () => {},
            onMouseLeave: () => {}
        }; // second approach for typescript
    
        rectangleControls.onMouseEnter = () => {
            if (rectangleRef.current) {
                setIsMouseInRectangle(true);
                rectangleRef.current.classList.remove("bg-green-900");
                rectangleRef.current.classList.add("bg-red-900");
            }
        }
        rectangleControls.onMouseLeave = () => {
            if (rectangleRef.current) {
                setIsMouseInRectangle(false);
                rectangleRef.current.classList.remove("bg-red-900");
                rectangleRef.current.classList.add("bg-green-900");
            }
        }
    
        return { rectangleControls, isMouseInRectangle };
    }
    

    Edit: Explaining the changes made in the hook. Here I have created an Object rectangleControls and converted the onMouseEnter, onMouseLeave to arrow function and assign them as key to rectangleControls, this will help you achieve what you are looking for exporting a single variable.