Let’s play with React Hooks: From a Function Component to a Function Component with State and Animation
When first reading about hooks a couple months ago, I was a bit intimidated. I had fallen into a routine when it came to creating React Components and was (and still am) very comfortable using React Classes, and I wasn’t sure I wanted or needed React Hooks. But as I’ve done a little more digging I’ve found that hooks are pretty useful and I’m excited to use them for future projects.
So what are Hooks?
React Hooks offer a way to improve the usability of Function Components; it’s easier to explain what they do than what they are.
React Hooks:
- Expose Function Components to more, powerful React features
- Allow reusability of stateful logic across components
- Permit developers to write more organized code, which decreases the complexity (and increases readability) of components
- Encourage better coding practices than Class components permit
Many third-party libraries have their own hooks, but for the most part all of them are composed of React’s built-in hooks.
Basic React Hooks
The two most basic and popular hooks are the useState
and useEffect
hooks. They allow your function components to utilize the main React features they were lacking — stateful logic and component lifecycles.
useState()
This hook lets your components hold state! That means no more re-writing a component into a Class just so it can internally keep track of one thing. Using it is extremely simple and intuitive.
const [color, setColor] = useState(‘blue’)
Here we declare color as part of our component state and give it an initial value of ‘blue’. The same hook also defines and returns a function to update that value (similar to setState()
) called setColor
. When we want to update our color we simply call setColor(‘green’)
.
The main limitation of this hook is its simplicity. Some components require more complex state, which the Class method setState()
handles well. When using the useState hook you’d need multiple calls to useState()
and multiple setter functions. Thankfully the React developers have a solution for that, namely the useReducer hook.
useEffect()
With this hook all our components can react to lifecycle events! This one is a triple-edged sword, but unlike a triple-edged sword it is safe to use. It can replicate the three most-prevalent lifecycle methods: componentDidMount
, componentWillUnmount
, and componentDidUpdate
. It is also commonly paired with the useState
hook.
const [lightSwitchOn, setLightSwitchOn] = useState(false)
useEffect(() => {
setLightSwitchOn(!lightSwitchOn)
})
The first parameter is a function that will run on every update (including the first render). As it is, the lightSwitchOn
state will switch between true/false on every re-render. What if we only want it to switch values when a certain prop changes? That’s where the second parameter comes in.
...
useEffect(() => {
setLightSwitchOn(!props.sunIsUp)
}, [props.sunIsUp])
The effect will only run when it detects a change in the props.sunIsUp
value. And since it’s an array you can have the effect run after a multitude of different updates. But what if we still wanted to run the effect conditionally?
...
useEffect(() => {
if (!props.houseIsEmpty) {
setLightSwitchOn(!props.sunIsUp)
}
}, [props.sunIsUp, props.houseIsEmpty])
Now the effect will only run when props.sunIsUp
updates, but still wouldn’t do anything unless props.houseIsEmpty
is false. You may have thought to conditionally call the useEffect
hook itself (put useEffect
inside of an if ()
statement), but that would be breaking one of the delicate rules of hooks. In fact that is how hooks work in the first place — preserving their call order to make sure subsequent re-renders are mapped correctly.
Function Components with Hooks
Recently I’ve found that the more I developed with React-Native the less I was using Function Components. Even if I did start out creating a Function Component it usually had to be refactored into a Class Component anyway. I saw them as a dying breed, but just when I was beginning to lose hope they were pulled up from the depths of limbo — by hooks.
Using Hooks to add State and Animation to a Function Component
In this example I’m going to walk you through adding state and animation to a function component using hooks. I’m making use of some es6 syntax and patterns including destructuring and arrow functions. I’ve also grown used to defaulting to PureComponent when creating a class component. Initially I thought I would lose the improvements this offers when moving to function components and hooks, but fortunately that’s not the case. React.memo()
is a higher-order component (HOC) that allows your function components to mimic the optimizations that PureComponent offers.
Function Component
Let’s create a simple component that we’d typically make as a Function Component. In this example we’ll make a prompt-like view, something that appears on top of the user’s screen that provides choices for them to select. Upon selecting a choice the entire prompt is hidden and the choice is passed to the parent. The code for the screen/container and styles is included in this Gist.
Function Component with State
That works just fine. But now instead of selecting a choice to hide the prompt, we want the user to select a choice, then submit the prompt. This allows the user to change their selection until they hit a Submit button.
First I import useState
and made a call to it in Prompt. onChoose()
now sets the local state while onSubmit()
takes care of committing the selection. Within the JSX I added some styling to differentiate between selected and non-selected choices and a submit button for the user to tap.
Before hooks we’d have to refactor this component into a Class, or pass more props down from the parent - just to keep track of some simple state. Now we can use the useState()
hook to do that!
Function Component with State and Animation
The last thing we want to add is an animate in/out effect — to give the prompt some polish.
Before hooks, there were a lot of ways we could have added animation. But I’m thinking since we 1) may want to re-use this animation, 2) want to keep our component hierarchy the same, and 3) are already working within a function Component, then hooks are a great choice.
I’ll create this useHookedEffect()
animation effect as a separate hook, so that other components can utilize it.
In short, I'm calculating transform values to use for the shown and hidden states. I animate the passed in reference whenever props.visible
value changes. Lastly, I added a little hook image for some pizzazz. This is how we would use the HookedEffect
in our current Prompt component.
Make sure the outermost <View>
is turned into <Animatable.View>
and attach the hookedRef
to that. Otherwise, you'll get an error.
Great. Now we have a prompt that is stateful and has a nice animation to it. Our final code isn’t too different from our initial code either. Replicating this example using a Class Component would have lead to verbosity, and the logic in-place (both stateful and animation) would not have been easily re-usable.
Here's what our final example looks like to a user:
When and how should you use Hooks?
Hooks are definitely worth exploring; they show a lot of promise in terms of code readability and ease-of-use. The React team has a great rundown of how you should adopt hooks.
In terms of how to think about hooks for older projects, I wouldn’t go so far as to say you should re-write your old Class Components to replace them with Function Components and hooks. If you already have working components then it’s harder to refactor them. If you’re actively adding features to a project then slowly refactoring your classes into functions would be an option — just be sure to start out with your simpler components and work your way up.
At the end of the day, there’s nothing a class component can’t do that a function component with hooks can. Classes aren’t going anywhere, and there may be times when we still opt to use class components over function components.
That said, I think hooks are great, and will likely only become better as the community continues to develop and release new hooks. Hooks provide a lot of utility and lead to clearer, more readable code. We plan on embracing hooks generously for all upcoming apps and features.
Header photo by chuttersnap on Unsplash