Custom Hooks in React: Flexibility and Reusability Unleashed

3 Min Read

blog img

Greetings developers! As we continue our journey into the depths of JavaScript and its frameworks, today we’ll explore a unique and powerful feature of React: Custom Hooks 😎. This core concept reshapes the way we manage state and side effects, streamlining our code, and improving reusability across components.

Beyond Built-In Hooks

Custom Hooks in React let us extract and encapsulate logic from function components, allowing for code sharing and reuse across multiple components. This leads to cleaner, more maintainable code that adheres to the DRY (Don’t Repeat Yourself 🔁) principle.

Let’s say we often need to fetch data from an API and use it within our components. Instead of repeatedly writing useEffect and useState in each component, we can create a custom hook:

const useFetch = (url) => { 
  const [data, setData] = useState(null) 
  useEffect(() => { 
    fetch(url) 
      .then(response => response.json()) 
      .then(data => setData(data)) 
  }, [url]) 
 return data 
}

This useFetch Custom Hook takes a URL as an argument, performs the data fetching operation, and returns the fetched data.

Our components become more concise as we utilize our custom hooks:

const Component = () => { 
  const data = useFetch('<https://api.example.com/data>') 
  return <div>{data}</div> 
}

This implementation is not only clean and simple but it also allows us to reuse the same data fetching logic in other components merely by invoking the useFetch hook.

Furthering Flexibility with Custom Hooks

Custom Hooks are flexible. They can handle any logic that built-in hooks can: stateful logic, side effects, context, reducers, and more. For instance, you can create a custom hook to manage form inputs:

const useFormInput = (initialValue) => { 
  const [value, setValue] = useState(initialValue) 
  const handleChange = (event) => { 
    setValue(event.target.value) 
  } 
  return [value, handleChange] 
}

Now, any form element in your application can benefit from this hook:

const FormComponent = () => { 
  const [name, handleNameChange] = useFormInput(''); 
  const [email, handleEmailChange] = useFormInput(''); 
  return ( 
    <form> 
      <input type="text" value={name} onChange={handleNameChange} /> 
      <input type="text" value={email} onChange={handleEmailChange} /> 
    </form> 
  ); 
}

This flexibility, combined with their ability to be combined, makes custom hooks a powerful tool in the React toolbox.

Extending Custom Hooks: Abstracting Libraries

Custom Hooks can abstract and share instances of libraries across different components. They can encapsulate not just state logic or side effects, but also the logic related to the usage of external libraries.

Consider the case in which various components in our application are harnessing the power of Amplitude, a robust product analytics resource I’ve previously unpacked in a previous post. Let’s delve into a tangible example that demystifies how we can optimize this process by applying a Custom Hook that embodies the functionality of Amplitude.

The Scenario

import * as amplitude from '@amplitude/analytics-browser' 
 
const ButtonA = () => { 
  return ( 
    <button onClick={()=> amplitude.track('BUTTON_A_USED')}> 
      Press me! 
    </button> 
    ) 
 } 
// in another file you may have: 
import * as amplitude from 'amplitude-analytics' 
 
const ButtonB = () => { 
  return ( 
    <button onClick={()=> amplitude.track('BUTTON_B_USED')}> 
      Press me! 
    </button> 
    ) 
}

In this approach, both ButtonA and ButtonB components are interacting independently with an Amplitude instance, leading to code repetition.

Refactoring with a Custom Hook

Instead of duplicating the code across the components, we can introduce a Custom Hook that abstracts the Amplitude functionality, facilitating centralized management:

import * as amplitude from '@amplitude/analytics-browser'; 
// A detail is that this also significantly reduces the number of imports 
 
const useAmplitudeTracker = () => { 
  const trackEventA = () => amplitude.track('BUTTON_A_USED') 
  const trackEventB = () => amplitude.track('BUTTON_B_USED') 
  return {  
    trackEventA,  
    trackEventB, 
    //and this is extensible for all your needs 
    } 
}; 
 
export default useAmplitudeTracker

We can now refactor our ButtonA and ButtonB components to leverage this hook:

import useAmplitudeTracker from './your/route/for/hooks' 
 
const ButtonA = () => { 
  const { trackEventA } = useAmplitudeTracker() 
    return ( 
      <button onClick={()=> trackEventA()}> 
        Press me! 
      </button> 
      ) 
} 
import useAmplitudeTracker from './your/route/for/hooks' 
 
const ButtonB = () => { 
  const { trackEventB } = useAmplitudeTracker() 
    return ( 
      <button onClick={()=> trackEventB()}> 
        Press me! 
      </button> 
      ) 
}

Our components are much cleaner now. Any shared logic for Amplitude use is maintained in one place — the useAmplitudeTracker hook.

Benefits and Conclusion

Custom Hooks stand as a game-changing force in React development. They reshape our code into more flexible, maintainable, and elegant forms, enhancing reusability and upholding the DRY (Don’t Repeat Yourself) principle, is funny because im repeating it, but is important to take care of that principle 😆.

Embarking on the journey of creating your Custom Hooks empowers you to encapsulate any kind of logic. So, why not dive in, create your own hooks, and unlock the immense potential they offer?

As a JavaScript developer in an ever-evolving startup ecosystem, I will be sharing more of my experiences and insights. Stay updated and join me on this exciting journey:

- Connect with me on LinkedIn
- Explore my projects on GitHub

Let’s dive into the vast coding universe together. Happy coding! 😃