Writing React applications can get same-y. We often find ourselves going over the same patterns of defining the same component behavior over and over, and one can start to wonder how we can make these patterns more and more DRY.
(Before I get shouted down by C developers who’ll tell me a
_static_ variable in a function is effectively stateful, I know.)
Hooks do not only permit stateful behaviour, but they’re also reactive. You can use hooks to react to changes in the props of a component, store state, store references to mutable values, etc. And that’s just using the hooks shipped by default. Hooks are composable too. This means I can write my own hook, that uses other hooks. By combining these, One can effectively inject state, behavior or reactivity into any functional component.
In this article I’m going to go over a few examples of these, both for React and React Native devs, that demonstrate this composability and create some neat and super handy, easily testable bits of functionality that you can use in any project.
These hooks are usable for both React and React Native projects, as they are JS-only and use features available in both.
Quite often we may end up in situations where the consequences of a side-effect will unmount a component that’s expecting to make state updates. For example, let’s imagine the following example taken from an older codebase of mine:
Now, this component has a couple of issues that we won’t get into, but we should really be handling the case where
In any case, this is fine for our purposes. But what happens if
props.onClick causes the app to navigate to a new page? This button will unmount, and, consequently,
setLoading(false) gets called. In general, you should never update the state of an unmounted component, as this can cause memory leaks**.**
We need to know if the component is still mounted when we get to
setLoading(false) , as we need to skip it if the component has unmounted in the meantime.
Let’s write a hook that gives us that ref:
Now, we can use it in our previous example:
This hook’s helped me avoid some subtle bugs. Moving swiftly on -
If you’re building an app that contains any kind of real-time communication (chat, games etc), it’s likely that you’ll end up using WebSockets. They give you full-duplex (two-way) communication with another host, contrary to HTTP which adopts a Request-Response model.
Handling the state of your WebSocket in a React Component can be a bit of a pain, and you’ve always got to remember to close your WebSocket on unmount, etc. In short, this leads to a lot of duplication if you make use of WebSockets in many places in your app.
Let’s take a look at the hook:
This hook returns a ref which stores the active WebSocket in its
current property, and automatically closes it when the component using it unmounts, e.g.
This hook has lots of use cases for flexible WebSocket based applications, notably here chat, we can safely close the WebSocket when the chat is closed, opening it only when the chat is open.
Cool right? Given that we get a
ref to an actual
ws instance, it’s easy to add any extra behaviour we might want, using the usual WebSocket API.
This hook is a little time-saver. Imagine the scenario — you’ve got a component with
n buttons, all of which launch some async operation on press/tap/click etc. Keeping track of their loading states is a bit of a pain — you don’t want to have to maintain an object or array of loading booleans, as it’ll be easy to cause bugs — especially if the number of buttons can change over the component’s lifecycle! It looks like this:
Now you have a view on the loading state of your action, encapsulated into a hook — no more
useState cluttering your component, you’d do something like:
Because the wrapper makes sure to return the promise that your
async function returns, you can do all the usual things you’d want to do with a promise (
await , promise chaining, etc.).
Here’s a hook we can use in React Native applications to make our lives easier.
EDIT: This tutorial from the
react-navigationdocs provides a more declarative version of this hook for achieving the same effect.
When writing a React Native app using React Navigation, we often run into a particular problem. In short, the device’s status bar is controlled by rendering a
StatusBar component. But if we have an app with multiple screens, where more than one screen is rendered at once, rather than being lazy-loaded, this approach may fail. This is because rendering this
StatusBar updates the config under the hood, but only reflects the state when it was last rendered. What this means is if your app has screens A and B, even if you have A visible, if B rendered after A, the status bar will have the config from screen B, which is wrong for screen A.
StatusBar module from React Native also exports imperative means of setting this config. So what if we could write a hook that makes these imperative calls when a screen focuses, so that we always have the right config? This is a hook I wrote for a project that does just so:
Only drawback here is it’s not super future-proof for API changes.
Now we can set the config on each screen with just a call to
useStatusBar and it’ll always be correct when the screen becomes focused. This hook-based alternative helps keep your markup clear of rendering
StatusBar in every screen.
Going Further — useAppearanceAwareStatusBar
What if we have an app that supports both dark and light modes using the phone’s color scheme + preferences, which could change behind the scenes? We can go one further:
A little more complicated, but super powerful!
Now if the user changes their color preference in your app’s settings, or, for example, the sun sets and your phone’s color scheme changes, this
StatusBar will be aware of it and match the rest of the app. Here obviously we’re not taking into account the other methods in the
StatusBar API, which would be a welcome enhancement.
As mentioned before, React apps can get same-y. We often find ourselves maintaining the same behavior for lots of components, and hooks are a great way to reuse behavior and make maintenance and iteration speed lightning fast.
Thanks for reading!