Here, we'll explore loosely coupling react hooks with dependency inversion as a way to manage side effects.
NB: The hooks code given here, uses react-query hooks for simplicity cos I'm rushing to write this. I hope that's not a problem. Perhaps, someone will write the equivalent implementations using out-of-the-box react hooks.
We'll consider a simple react hook, that fetches a list of users in two ways:
const useFetchUsers = () => {
const { data } = useQuery("cache:users", () => fetch("https://api.example.com/users"));
return data;
};
Using this is simple:
const users = useFetchUsers();
But we've lost the ability to explicitly specify how the hook should fetch users, without using a mocking library.
/**
* @param {() => Promise<any[]>} fetchUsers
*/
const useFetchUsers = ({ fetchUsers }) => {
const { data } = useQuery("cache:users", fetchUsers);
return data;
}
Here, we pass fetchUsers
as a dependency in the props. This means, our hook is not tightly-coupled to a particular way of fetching users.
To use, we write:
const users = useFetchUsers({
fetchUsers: () => fetch("https://api.example.com/users")
});
The main difference between the two is having implicit vs explicit dependencies. I believe explicitly declaring dependencies, better adheres to the functional paradigm that react tries to emulate, and makes for more testable code.