Tessian’s mission is to secure the human layer by empowering people to do their best work, without security getting in their way.
I’d like to describe Tessian’s journey with React hooks so far, covering some technical aspects as we go.
About two years ago, some of the Frontend guild at Tessian were getting very excited about a new React feature that was being made available in an upcoming version: React Hooks.
React Hooks are a very powerful way to encapsulate state within a React app. In the words of the original blog post, they make it possible to share stateful logic between multiple components. Much like React components, they can be composed to create more powerful hooks that combine multiple different stateful aspects of an application together in one place.
The answer could be found in the way we were writing features before hooks came along. Every time we wrote a feature, we would have to write extra “boilerplate” code using what was, at some point, considered by the React community to be the de facto method for managing state within a React app ─ Redux. As well as Redux, we depended on Redux Sagas, a popular library for implementing asynchronous functionality within the confines of Redux. Combined, these two(!) libraries gave us the foundation upon which to do…very simple things, mostly API requests, handling responses, tracking loading and error states for each API that our app interacted with.
The overhead of working in this way showed each feature required a new set of sagas, reducers, actions and of course the UI itself, not to mention the tests for each of these. This would often come up as a talking point when deciding how long a certain task would take during a sprint planning session.
Of course there were some benefits in being able to isolate each aspect of every feature. Redux and Redux Sagas are both well-known for being easy to test, making testing of state changes and asynchronous API interactions very straight-forward and very ─if not entirely─ predictable. But there are other ways to keep testing important parts of code, even when hooks get involved (more on that another time).
Also, I think it’s important to note that there are ways of using Redux Sagas without maintaining a lot of boilerplate, e.g. by using a generic saga, reducer and actions to handle all API requests. This would still require certain components to be connected to the Redux store, which is not impossible but might encourage prop-drilling.
In the end, everyone agreed that the pattern we were using didn’t suit our needs, so we decided to introduce hooks to the app, specifically for new feature development.
We also agreed that changing everything all at once in a field where paradigms fall into and out of fashion rather quickly was a bad idea. So we settled on a compromise where we would gradually introduce small pieces of functionality to test the waters.
I’d like to introduce some examples of hooks that we use at Tessian to illustrate our journey with them.
Our first hook was usePortal. The idea behind the hook was to take any component and insert it into a React Portal. This is particularly useful where the UI is shown “above” everything else on the page, such as dialog boxes and modals.
The documentation for React Portals recommends using a React Class Component, using the lifecycle methods to instantiate and tear-down the portal as the component mounts/unmounts. Knowing we could achieve the same thing with hooks, we wrote a hook that would handle this functionality and encapsulate it, ready to be reused by our myriad of modals, dialog boxes and popouts across the Tessian portal.
The gist of the hook is something like this:
Note that the hook returns a function that can be treated as a React component. This pattern is reminiscent of React HOCs, which are typically used to share concerns across multiple components. Hooks enable something similar but instead of creating a new class of component, usePortal can be used by any (function) component. This added flexibility gives hooks an advantage over HOCs in these sorts of situations.
Anyway, the hook itself is very simple in nature, but what it enables is awesome!
Here’s an example of how usePortal can be used to give a modal component its own portal:
Just look at how clean that is! One line of code for an infinite amount of behind-the-scenes complexity including side-effects and asynchronous behaviors!
It would be an understatement to say that at this point, the entire team was hooked on hooks!
Two months later we wrote hooks for interacting with our APIs.
We were already using Axios as our HTTP request library and we had a good idea of our requirements for pretty much any API interaction. We wanted:
Our real useFetch hook has since become a bit more complicated but to begin with, it looked something like this:
To compare this to the amount of code we would have to write for Redux sagas, reducers and actions, there’s no comparison. This hook clearly encapsulated a key functionality that we have since gone on to use dozens of times in dozens of new features.
From here on out, hooks were here to stay in the Tessian portal, and we decided to phase out Redux for use in features.
Today there are 72 places where we’ve used this hook or its derivatives ─ that’s 72 times we haven’t had to write any sagas, reducers or actions to manage API requests!
I’d like to conclude with one of our more recent additions to our growing family of hooks.
Created by our resident “hook hacker”, João, this hook encapsulates a very common UX paradigm seen in basically every app. It’s called useSave.
The experience is as follows:
Each of these aspects require the use of a few different native hooks:
A hook to prevent the window from closing/navigating if changes haven’t been saved yet (useEffect)
Here’s a simplified version:
As you can see, the code is fairly concise and more importantly it makes no mention of any UI component. This separation means we can use this hook in any part of our app using any of our existing UI components (whether old or new).
An exercise for the reader: see if you can change the hook above so that it exposes a textual label to indicate the current state of the saved object. For example if isLoading is true, maybe the label could indicate “Saving changes…” or if hasChanges is true, the text could read “Click ‘Save’ to save changes”.
Thanks for following me on this wild hook-based journey, I hope you found it enlightening or inspiring in some way. If you’re interested in working with other engineers that are super motivated to write code that can empower others to implement awesome features, you’re in luck! Tessian is hiring for a range of different roles, so connect with me on LinkedIn, and I can refer you!