I have years of experience with programming in Java/Android/C#/Nodejs/Angular/React but stopped coding React before hooks were popular, now I’m back and I don’t really understand why people are so obsessed with custom hooks now (which seems ridiculous since for me main strength of React/Redux was always explicit simplicity in comparison to other frameworks, I think React with couple simple class lifecycle methods used to be very intuitive at its core before they introduced hooks, especially with Typescript interfaces).
Pretty standard way to achieve reusability of code in almost every current language is to refactor things to simple pure functions to external services that can be easily exported/imported and tested without needing to mess with components/framework elements (so called "glue"). Many times whole application of "inversion of control" is just based on that (and interfaces).
Now in many places i read that "hooks are a new way of coding" and writing custom hooks for everything is a "good practice". Can someone convince me that its actually true and why? Right now it sounds to me like some kind of mambo jambo, classic "we wrote this new framework/library therefore its the best thing in the world".
I will give you simple example that I have seen in one of the tutorials.
Back then it was something like that:
componentDidMount(){
document.title = `You clicked ${count} times`;
}
componentDidUpdate(){
document.title = `You clicked ${count} times`;
}
or after refactor just:
componentDidMount(){
documentService.setTitle(`You clicked ${count} times`);
}
componentDidUpdate(){
documentService.setTitle(`You clicked ${count} times`);
}
Looked pretty fine to me, explicit intent in terms of what trigger actions was clear (component is triggering service, if we add Typescript interfaces we can have pretty nice inversion of control here). Many times service can be just passed in props. Its very simple thing and same logic can be used to lets say fetch data with async.
Then with hooks its something like that:
useEffect(() => {
document.title = `You clicked ${count} times`;
},[count]);
Now to me it looks pretty much same as previous lifecycle methods model. But instead two separate explicitly named lifecycle methods, we now have lifecycle that depends on combination of "useEffect" method and different combination dependency array (presence of dependency array changes trigger of method). Yes im aware that there is difference and internally hooks work more like events but goal is more or less the same, to trigger actions/renders based on some changes in component.
Thats ok, and in this case i would do same refactor to service as before like this:
useEffect(() => {
documentService.setTitle(`You clicked ${count} times`);
},[count]);
But now I have a problem. I read in many many places that people actually dont do that and they do custom hooks for everything now, something more or less like this:
useDocumentTitle = (title: string) => {
useEffect(() => {
document.title = title
}, [title])
}
useDocumentTitle(`You clicked ${count} times`);
-
Then I read that it’s so much better and more testable. All I’m thinking is "HOW?". Now instead simple functions in service we have useEffect hook that is just detached from component but it doesn’t really change anything since "useDocumentTitle" has the same render lifecycle as useEffect used internally?
-
So now I need to remember that function useDocumentTitle can trigger rerender of component but i dont know when it will fire trigger without peeking into this function since logic is based on dependency array of useEffect that is used internally?
-
How it is easier to test useDocumentTitle than service or just extracted method? It seems harder to me.
2
Answers
I will not go into details but I will just give one example on why hook makes the code cleaner:
Imagine you have a component that will perform some side effects:
When you have more than 1 side effects, it is easy to forget to cleanup something you have done when you mount your component. But with hooks, you can put both the creation and clearing of side effects together:
This also makes it easier to make shared libraries because now all you need is a custom hook that does the
useEffect
above.(There’s one thing missing in your original post is the idea of cleanup. When a React component is unmounted, it is the developer’s responsibility to ensure all side effects caused by this component should be reverted back to the original state)
useEffect is a complicated topic, the React docs have 6 pages that talk about it. I’m not sure who is saying a custom hook is so much better than a single useEffect but it depends.
If you just have a single isolated case (or even a bit of duplication) where you need useEffect to do something specific in one component then you absolutely do not need a custom hook.
Custom hooks are meant to share logic across components, reduce duplication and help you abstract logic. Abstracting logic keeps you from worrying about ‘how’ something is implemented, allowing you to take a more declarative approach by telling the component ‘what’ to do.