Sharing Stateful Logic in React with Hooks: A Better Way to Build Reusable Components

Juntao Qiu
ITNEXT
Published in
4 min readFeb 20, 2023

--

Although React hooks have been available for some time, I have noticed that certain fundamental concepts — such as the reasons for their necessity — are not always well understood. Therefore, I would like to revisit the problem and explain why hooks are an excellent solution for eliminating the duplication of stateful logic in React code.

You can download a cheat sheet here containing the most common React hooks with examples and illustrations.

The React hooks cheat sheet

The component pattern for building User Interface

When you are building a component-based UI system, you’ll need some kind of life-cycle callback: you need to be able to register functions that will be called automatically when the framework needs to. For example, when the framework initialised your component with some properties, or when the component is not needed on the page and needs to be garbage collected, etc.

You would see something like this:

class MyActivity extends AppCompatActivity {
@Override
public void onCreate(...) {
}

@Override
public void onStart() {
}

@Override
public void onStop() {
}
}

Or with the traditional React classes:

class MyComponent extends React.Component {
componentDidMount() {
}

componentWillUnmount() {
}

render() {
}
}

The duplication problem

And in times, you will encounter some duplications in different components — they perform the same thing but scatted in different components.

For example, you need to fetch some data, manage the loading and error handling stuff in one component, and soon find you need the same logic in another component. Another common case is event handlers, you need to register an event handler in one component and unregister it on unmount, and you need the same functionality in 12 different components.

Although you can use Higher-Order-Component (HOC) pattern to solve the problem, it leads to another problem: they need to change the component structure, and if you have many cross-cutting HOCs, the component hierarchy will be messed up quickly.

import withAuth from './withAuth';
import withLogger from './withLogger';

function MyComponent() {
return (
<div>
{/* ... */}
</div>
);
}
export default withLogger(withAuth(MyComponent));

The hooks to the rescue

With hooks, you can extract stateful logic from a component so it can be tested independently and reused. Hooks allow you to reuse stateful logic without changing your component hierarchy.

— React documentation

Note here the keyword here is reuse stateful logic. Let me show you a quick example of what that means.

the similar behaviour of profile and settings

In our application, two components need a similar toggle function. A user avatar shows a green circle when a user is online and a grey circle if they’re offline. Also, a switch button that shows different states when toggled on and off.

On the UI, the switch control and profile have nothing in common. But if you examine their logic, they’re similar — they all need the toggle behaviour. Traditionally it’s pretty hard to implement these components without duplication of the state management logic.

But hooks can fix the issue elegantly. We can then extract the logic of toggle status and function into a shared place and use it whenever we need.

import { useState } from "react";

const useToggle = (initial: boolean = false) => {
const [state, setState] = useState<boolean>(initial);
const toggle = () => setState((state: boolean) => !state);
return { state, toggle };
};

export default useToggle;

The Bluetooth switcher will be something like this to respond to two different statuses:

const SwitchControl = ({ title }: { title: string }) => {
const { state, toggle } = useToggle(true);

return (
<div className="control">
<span>{title} </span>
<div
className={`switch switch-${state ? "on" : "off"}`}
onClick={() => toggle()}
>
<button></button>
</div>
</div>
);
};

On the other hand, the logic for UserProfile , by talking to the backend service, it can toggle the online state similarly.

const UserProfile = () => {
const { state, toggle } = useToggle(false);

useEffect(() => {
const checkState = async () => {
const user = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
).then((r) => r.json());
console.log(user);
if (user.status) {
toggle();
}
};
const id = setInterval(() => {
checkState();
}, 5000);
return () => {
clearInterval(id);
};
}, []);

return (
<div>
<h2>Profile</h2>
<div className="profile">
<Avatar cssClasses={[state ? "on" : "off"]} />
</div>
</div>
);
};

export default UserProfile;

More complicated state management

While sharing a small amount of data between two components may not be problematic, hooks can significantly simplify the management of network-related states in container components.

Several articles I published recently discuss the complexity of network-related states and how hooks can streamline their management.

Summary

At its core, React hooks are a great way to organise reusable stateful logic. They offer a simpler and more efficient way to manage component states and side effects, making it easier to build reusable and maintainable components. By encapsulating stateful logic in Hooks, developers can write cleaner and more concise code.

If you like the reading, please Sign up for my mailing list. I share Clean Code and Refactoring techniques weekly via blogs, books and videos.

I hope you enjoyed reading this. If you’d like to support me as a writer, consider signing up to become a Medium member. It’s just $5 a month, and you get unlimited access to all my articles on Medium — as well as all the articles of the mentioned writers and everybody else!

--

--

Writer for

I help developers write better code. Developer | Author | Creator. https://juntao.substack.com/ @JuntaoQiu