Search code examples
reactjsreact-hooksuse-effectcomposition

REACT: How to create a reusable custom hook with functions and effects that can change & react to changes in a host component's data


I have a react App component for editing Order information (customer name, items, address, etc.)

  • The address is shown using a sub-component that takes Order data & event handlers as properties.
  • Changes to address data are handled in the host component (App) by useEffects.
  • There are also address-related helper functions that can be invoked.
  • Other App sub-components also take the Order data to work with other parts of it.

Here is a simplified version...

function App({ orderInfo }) {

    // App state ('data' has customer name, address, user role, etc.)
    const [data, setData] = useState(orderInfo);    

    // Address-related functions & effects:
    function isEditableAddress() { return data.user.role === 'EDITOR' }

    useEffect(function loadSuburb() {
        // (Load suburb when postcode changes)
        (async () => {
            const response = await fetch(`/myApp/getSuburb.do&postCode=${data.postcode}`);
            const suburb = await response.json();
            setData((prev) => ({...prev, suburb: suburb}));
        })();
    },[data.postcode]);
    
    // -- Non-address functions & effects:
    // [here go more functions & effects that also work with 'data'...]
    
    // Sub-components, one of which works with address...
    return (
        <>
            <AddressComponent
                data={data}
                isEditableAddress={isEditableAddress()}
                onChangePostcode={val => {setData((prev) => ({...prev, postcode: val}))}}
            />
            <OtherComponent
                data={data}
                onChangeFirstname={val => {setData((prev) => ({...prev, firstname: val}))}}
            />
        </>
    );
        
}

I now have another App that needs to use AddressComponent and the same address effects & functions (of which there are many more than in the example above) so I am inclined to move the these into... a custom hook?

My questions are:

  1. How do I get 'useEffect(fn, [data.postcode])' to react to and update data in the parent component once it's inside its own custom hook? (I tried passing 'data' and 'setData' as properties into my useAddress custom hook with no success.)

  2. How do I expose the helper functions to the parent component? Do I just return them from the custom hook, and set them in the home component like: {isEditableAddress} = useAddressHook();

Or am I expecting to much from a custom hook? What is the best approach to this? I'm struggling to find examples of a custom hooks that work this way.


Solution

  • Custom hooks are designed to do exactly what you need^ to isolate and reuse its logic. You can read more about building custom hooks in official React documentation. Your case is typical case for building custom hook: isolate state, effect and some couple of functions to operate over state in one hook. You can do it this way:

    const useOrderData = (initialState) => {
      const [orderData, setOrderData] = useState(initialState)
    
      useEffect(() => {
        // load suburb
        setOrderData()
      }, [data.postcode])
    
      // other effects in case you need them
    
      const isEditableAddress = orderData.user.role === 'EDITOR'
    
      return [orderData, { isEditableAddress }]
    }
    

    You can also add to custom hooks some functions, i change isEditableAddress to variable cause its value will update every time data changing. Using hook will looks like this:

    const [data, { isEditableAddress }] = useOrderData({})
    

    Check this site, there is a lot of custom hook examples