The power of React hooks (part 1)
So at the time of writing React have recently released the hooks feature spec to the stable release of react (v16.8.0) and are definitely causing a stir in the developer world.
The aim of hooks is to allow functional components to access features that were previously only possible in class components (namely lifecycle methods and state) in a way that is reusable. The main thinking behind this is that by avoiding some of the previous bulk needed for these features on class components, code can become cleaner and therefore more readable and maintainable (to some extent).
So with all this in mind I started our latest project with the aim to not use a single class component. Disclaimer: I am not saying everyone should do this, I just wanted to see how far I could get with new hooks scope.
It’s also worth noting before continuing that all the examples below will be using Typescript.
Time range picker component
As part of the project we needed a custom time range picker component and this was where I leveraged the hooks API the most so for the sake of demonstration this is the component I will talk through. As it’s used within my project there are a couple of other components which this component in turn uses e.g TimeInput.
The old way
Below is an example of the class component equivalent of what I am aiming to build.
Ok so that was pretty lengthy right?! So how can we begin to compress this down? Hooks to the rescue!
The first hook we are going to look at is useState. This hook allows us to manipulate state without extending Component.
The useState function will always return an array where the first item will be the current state and the second will be a dispatch action we can use to set that state. Whatever we set in the parentheses will be the initial state. we can use array destructuring to get the state and dispatcher properties as shown in the example below.
So let’s set up our 3 initial state values.
So useState has allowed us to remove the initialState declaration. We also no longer have access to this.setState() as we are in a functional component. This is why we need the second setSelected part. The function basically allows us to easily set the state for that property.
So within out return we might have something like this now:
The changeHour, changeMinute and getMinutesByQuarter functions are now outside the scope of component and can now be reused anywhere else in our app that we like. While not necessary for this component in larger projects and more complex components the more code we can extract out and test separately the better. While nothing stopped us doing this in the past, with class components I think it was easy to make component class functions dependant on `this`.
So now we have our state and state mutators working, but one part that didn’t get addressed in the above code was the componentDidMount() function. This is where useEffect comes in. The Effect hook is a function that allows you to handle side effects of your functional component (hence the name). It can be used to replace componentDidMount, componentDidUpdate and componentWillUnmount.
In my opinion the useEffect hook doesn’t upgrade our component much when replacing the componentDidMount or componentWillUnmount lifecycle methods. However you may have noticed the empty array as the second argument. This allows us to run our hook code only when a property updates similar to componentDidUpdate but now we can be really specific about when to run the code.
e.g. To run some code whenever the `selected` property changes it might look like this
Gone are the days of massive if/else statements within componentDidUpdate to figure out which property changed!
Another new hook which I love is useReducer. Since react and redux have become so heavily used together the guys at react have made useReducer part of the new spec. Reducers are a great way of dealing with more complex state changes, for example mutating state several levels deep or mutating multiple state values dependant on each other. The basic syntax for our state in a reducer would look something like this:
A simple switch statement to mutate the values of state. In this example it’s probably overkill to use a reducer but we can see how we can now set the startTime and endTime based on the current state of selected.
Ok so great, that’s a reducer but to use it in our component we need useReducer.
The above will on mount of the component set the selected state value to be whatever the ititialSelect prop is, simple right!
So I have gone through the 3 hooks I found most useful when building a web app with functional components but there are actually way more you can use.
Another core hook is useContext which uses the power of context api to functional components. In this project I didn’t get a chance to use the useContext hook as I took a shortcut with redux, but I can see how useContextcombined with useReducer would make it very simple to make your own version of redux with functional components. Pretty awesome!
On top of this you can see the other hooks available here. You can even make your own hooks to handle any hook functionality you may be repeating. In part 2 we will explore how to use a custom hook to specifically handle async Axios requests. When using custom hooks, it's really important to make sure the hook name is clear and properly describes the functionality of the hook.
Hooks are great! From completing a whole project using no class components I now struggle to see much use for class components. Hooks allow us to encapsulate the stateful logic independent from the component. This makes code so much more reusable across our components and eventually I can see a world where the jsx/tsx files are purely used to render UI and the core logic is extracted out of the component function.
Writing functional code is far easier to test and in my opinion much more readable. As always when such a big change comes to such a big developer community there is some pushback. Most of this pushback comes in the form of “Hooks aren’t clear” and I think thats because some of the functionality is slightly more hidden than in a class component. This could make things slightly more confusing but I think if you try it out on a small project you would be surprised how easy it is to completely get rid of class components.
All in all I will be pushing forwards with hooks on all my new React ventures.