Modern Front-end Development: Trends and Best Practices
Back in 2018 the React Hooks proposal changed the way we all write React code. The community as a whole has given it an extremely positive response and we can now write full application without relying on Class Components, we can further harness the power of hooks in our applications to reuse code effectively and distribute stateful functionality in our application. Let’s form a mental model to develop our own hooks for our stateful logic! I have hosted a webinar on the same topic, watch it if you are more of a ‘video’ person
Prerequisites
Here are some concepts you should be knowing before you make full use of this blog post
- Basic knowledge of React and writing Functional Components
- Basic Hooks such as useState and useEffect
- JS basics
Getting Started
for a great interactive way to get started is by cloning the react-hooks session repo
git clone https://github.com/antstackio/react-hooks-webinar.git
alternatively you can find the code sandbox here
The codebase looks like this:
src
┣ scaffold
┃ ┣...
┣ 1.jsx
┣ 1_class.jsx
┣ 2.jsx
┣ 3.jsx
┣ 4.jsx
┣ index.css
┣ main.jsx
┣ useDebounce.js
┗ useInput.js
Lets revise a hook
take a look at 1.jsx it says lets make a counter, which we can make using a simple useState
hook
import React, { useState } from "react";
const StageOne = () => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount((prevCount) => prevCount + 1);
};
return (
<div>
<div>Count {count}</div>
<button onClick={incrementCount}>Click Me!</button>
</div>
</div>
);
};
export default StageOne;
This is how it looks, neat right! but you already know this
Lets make our first custom hook!
When we say custom hooks we do not actually program a new react feature, but actually compose a new functionality by putting meaningful pre-existing hooks!
Time to Toggle!
We often need a hook for toggling a boolean value but it comes with a boilerplate code and often is messy when dealing with multiple toggles. Let’s create a useToggle
hook!
// useToggle.jsx
const useToggle = initialValue => {
const [state, setState] = useState(initialValue && true)
const toggle = () => {
setState(prevState => !prevState)
}
return [state, toggle]
}
We can consume it as
const [booleanState, toggleFunction] = useToggle(true) // optional param
// toggle booleanState by simply calling the toggleFunction
toggleFunction()
Multiple Toggles are made easier
const [booleanState1, toggleFunction1] = useToggle(true)
const [booleanState2, toggleFunction2] = useToggle(false)
const [booleanState3, toggleFunction3] = useToggle(true)
// All independent states
Abstracting Repeated Logic to hooks
Implementing logic for something line a text input has some logic that can be abstracted into a custom hook so that we can have common functionality isolated and reduce the scaffolding
// useInput.tsx
const useInput = () => {
const [value, setValue] = useState("")
const handleChange = e => {
setValue(e.target.value)
}
return {
value,
handleChange,
}
}
// All inputs in your app can use this, future changes/tweaks can reflect everywhere.
Saving $ with hooks
While this was a rather simple useCase, there are a lot of ways to use the power of hooks and make complex logic re-usable. Let’s take something like De-bouncing for example.
Debouncing is actually a technique derived from electronics where sending voltages continously to a component might damage it so it involves sending a signal only when there is a ‘calm’ after certain actions.
In software we use it to save money!, usually a website has a search box with suggestions, each call invokes a api call that runs an expensive function behind the scened causing money lost in bandwidth and cpu time. The best way to optimize this would be to wait for 100 or 200ms after the user has input their initial key-word to fire that call. Let’s implement this via a custom hook!
Time to DeBounce
import { useCallback } from "react"
export const useDebounce = (delay, callback) => {
const debounce = function (d, fn) {
let timerId
return function (...args) {
if (timerId) {
clearTimeout(timerId)
}
timerId = setTimeout(() => {
fn(...args)
timerId = null
}, d)
}
}
const debouncedCallback = useCallback(debounce(delay, callback), [])
return debouncedCallback
}
Above code implements debouncing in a simple way.
We use setTimeout to set a timeout equal to delay provided. if function is called more than once the timerId is re-set until there is moment of calm and the setTimeout executes properly. We memoize this function to avoid re-rendering it if its parent re-renders.
we consume it as
const callback = () => {
console.log("I have been Called")
}
const debouncedAPICall = useDebounce(500, callback)
//wont call debouncedAPICall until there is 500ms of calm
Add this to something like an input
<input onChange={debouncedAPICall}></input>
// Will print only after 500ms of user typing on input
And there you have it, custom hooks, the why, what and how. We saw a couple of pre-existing hooks, combined them to serve our own custom use-case and made something as complex as debouncing pretty easy.
Ending Notes
Hopefully this blog clears the fear of custom hooks out of you and helps you write better code. I have implemented the practice of separating all logic into a hook and keeping presentation layer separate in my projects and it has given great results with extremely clean codebase that can be easily maintained and minimal repeated code. I hope you also implement this is your codebase to get a boost in your overall Developer Experience.The possibilities opened by custom hooks are truly limitless.