React Hooks, Why, What and pros vs. cons

At the start of 2019, and as expected, the new React version 16.8.0 shipped with the new stable implementation of React Hooks.
These hooks will be a game-changer for the React community. Before starting to use React Hooks, we should ask some very useful questions that will help us to understand well this new feature.
First things first, why did the React Team add this feature? What problem will React Hooks solve? Why hooks and not other things?
Yes, this is what we should ask first; usually, everybody gets excited about new things and starts using them without understanding the reasons behind their existence.
Since not all new things are good for your projects, and your team …, these questions will help you be confident in your choices (when to use and how), and able to critique and clearly express your thoughts on them.
Let’s start with why. Why might we need to use React Hooks?
The main challenge we have when developing our React application is how to make the logic of some huge components reusable to share it between components and avoid duplication, which leads us to use design patterns that solve this problem, like HOC (higher-order component), render props, prop drilling, or mixins, all of which can lead to complex code that is difficult to write, improve, maintain, and test.
These design patterns are the most used to overcome this kind of challenge, but absolutely with a cost; since every solution has its cost, these approaches come with a complex mechanism to think about, an implicit method of implementation that makes the code hard to understand and co-locate.
React hooks give us the capability to use only functional components for all our needs over class components to avoid using these patterns.
Functional components are easier to read and test with less code. Before hooks, a functional component had a limitation since it didn’t have its local state and lifecycle methods, but now with React Hooks, it becomes possible to have local state and lifecycle methods, and finally stop thinking about the frightening ‘this’ keyword.
I recommend and encourage you to read these articles, so you can see the benefits of functional components:
Functional vs Class Components in React
React stateless functional components: nine wins you might have
React Hooks improve the development experience by using composition instead of inheritance. By relying on functions, not classes, they bring the missing piece of React: sharing logic between functional components. In a sophisticated way, Hooks make the need for patterns like prop drilling, render props, and HOCs unnecessary in most cases.
And as Sophie Alpert said at React Conf 2018:
- It’s hard to reuse stateful logic between components
- Complex components become hard to understand
- Classes confuse both people and machines
React hooks come to solve these nowadays problems:
- Wrapper hell (caused by design patterns: HOC, render prop, prop drilling)
- Huge components (that are hard to test and maintain, hard to co-locate code)
- Confusing classes (this keyword, hard to make it reusable)
Since the problems mentioned above are related to each other, we can’t solve them in isolation.
React hooks bring a new API that hits these three issues with 10 stones at a time so we can make sure they’re dead :).
In the longer term, we expect Hooks to be the primary way people write React components — React Doc
If hooks are going to be that important, why not learn about them now?
So what are React Hooks? What are these about?
They are collectively called hooks; they are special functions that add state, effect features, performance optimization, and more to your functional components.
There are 10 built-in functions that have unique names, all beginning with a three-letter ‘use’, but with different functionality and use cases. They are completely optional; you don’t have to rewrite your existing class, but you can start using them in the new ones since React is all about incremental adoption.
React Hooks are just functions that take an argument and return a value; in addition, you can create your own ones easily as a custom hook.
If you're wondering how these hooks work under the hood, I suggest to you these articles:
React hooks: not magic, just arrays
Under the hood of React’s hooks system
Before using and seeing them in practice, we have to be aware of the rules of using these hooks.
We have to respect these rules; otherwise, we will get confusion and unexpected results, since React relies on the order in which Hooks are called. The order of the Hook calls must be the same on every render.
React won’t natively throw errors or warnings to let you know; you have to add a linter plugin that ensures and enforces these rules automatically within your projects.
There are only two rules to follow.
- Only Call Hooks at the Top Level, i.e., not within conditionals, loops, or nested functions.
- Only Call Hooks from React functions, i.e., Functional Components and Custom Hooks.
Now let’s see them one by one, starting with the one you will use the most:
useState: This hook gives us the ability to make functional components have their local state through a very simple API, as you can see in the example below.
import React, { useState } from "react";
const Footer = () => {
const [extraInfo, setExtarInfo] = useState(false);
function handleClick() {
setExtarInfo(!extraInfo);
}
return (
<footer className="footer">
<div>
<small>Copyright © 2019</small>
</div>
<br />
<button onClick={handleClick}>More Info.</button>
{extraInfo && (
<p>
Le Lorem Ipsum est simplement du faux texte employé dans la
composition et la mise en page avant impression.
</p>
)}
</footer>
);
};
export default Footer;
useState example
useReducer: This hook gives us an easy way to organize our state management. If you are already familiar with Redux, you will find this easy to understand and work with.
function LoginUseReducer() {
const [state, dispatch] = useReducer(loginReducer, initialState);
const { username, password, isLoading, error, isLoggedIn } = state;
const onSubmit = async e => {
e.preventDefault();
dispatch({ type: 'login' });
try {
await login({ username, password });
dispatch({ type: 'success' });
} catch (error) {
dispatch({ type: 'error' });
}
};
return (
<div className="App">
<div className="login-container">
{isLoggedIn ? (
<>
<h1>Welcome {username}!</h1>
<button onClick={() => dispatch({ type: 'logOut' })}>
Log Out
</button>
</>
) : (
<form className="form" onSubmit={onSubmit}>
{error && <p className="error">{error}</p>}
<p>Please Login!</p>
<input
type="text"
placeholder="username"
value={username}
onChange={e =>
dispatch({
type: 'field',
fieldName: 'username',
payload: e.currentTarget.value,
})
}
/>
<input
type="password"
placeholder="password"
autoComplete="new-password"
value={password}
onChange={e =>
dispatch({
type: 'field',
fieldName: 'password',
payload: e.currentTarget.value,
})
}
/>
<button className="submit" type="submit" disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Log In'}
</button>
</form>
)}
</div>
</div>
);
}
function loginReducer(state, action) {
switch (action.type) {
case 'field': {
return {
...state,
[action.fieldName]: action.payload,
};
}
case 'login': {
return {
...state,
error: '',
isLoading: true,
};
}
case 'success': {
return {
...state,
isLoggedIn: true,
isLoading: false,
};
}
case 'error': {
return {
...state,
error: 'Incorrect username or password!',
isLoggedIn: false,
isLoading: false,
username: '',
password: '',
};
}
case 'logOut': {
return {
...state,
isLoggedIn: false,
};
}
default:
return state;
}
}
const initialState = {
username: '',
password: '',
isLoading: false,
error: '',
isLoggedIn: false,
};
The source of this code is from hswolff.
useEffect: This hook has been made for side effects; it does the same as the class lifecycle methods and more, with a different mental model which matches how the functional component is rendered.
import React, { useEffect } from 'react'
// useEffect API
/*
useEffect(() => {
Effect: subsciption, Api Call, setTimeout ...
return () => {
Clean up: unsubscribe, cancel api call, clear timeout ...
};
}, [Dependencies, State/Props property(ies) to track]);
*/
const Component = ({ bookId, source }) => {
// synchronize component rendering with this effect, no need for dependence(s)
// this effect will be triggered on each render
useEffect(() => {
// execute this just after the current rendre end
// EXP: subscribe to a service
const subscription = source.subscribe();
return () => {
// execute this just before the next rendre, this the place to unsubscribe the effect
// EXP: Clean up the subscription
subscription.unsubscribe();
};
});
// synchronize component rendering with this effect (empty dependence(s) [])
// this effect will be triggered on the first render
useEffect(() => {
// EXP: API call to get data
return () => {
// execute this just before the component is unmounted, because the empty dependence(s) []
// EXP: cancel the api call
};
}, []);
// synchronize bookId prop with this effect
// this effect will be triggered on dependence change [bookId]
useEffect(() => {
// get book details by bookId from API whenever bookId is changed
}, [bookId]);
return (<div>Handle side effects with useEffect<div>)
}
export default Component;
useEffect use cases
- useContext: This gives us the ability to pass data, config data in our app, and down the component tree without using prop drilling or a third-party library like Redux.
- useCallback: Gives us a nice performance gain by memorizing the given callback and updating it when the given dependencies change.
- useMemo: This hook caches some data, saving some computing time on our app and hopefully making it more responsive in the process.
See the other hooks here.
Pros vs Cons
Now it’s time to see the pros and cons of these React hooks.
Pros:
- A better alternative to some common design patterns.
- Easy to test and maintain (functional components).
- Create reusable, isolated components to avoid redundant logic.
- Easy to use and co-locate.
Cons:
- Have to respect its rules; without a linter plugin, it is difficult to know which rule has been broken.
- Need considerable time practicing to use them properly (Exp: useEffect).
- Be aware of the wrong use (Exp: useCallback, useMemo). You can read this article by kentcdodds
If we put the pros and cons in the balance, the advantages will outweigh the disadvantages, because the problems that the hooks solve are very heavy, and their disadvantages are easy to tackle and avoid.
Conclusion
To recap what we have learned, React Hooks came to solve a very common problem that everyone who uses React has; they encourage the use of functional components to benefit from their simplicity and efficiency for a scalable, maintainable, and testable codebase.
Since everything has its cons, React Hooks also have their own, and we have to be aware of and respect React team recommendations to avoid unexpected behaviors.
Note that:
“The main motivation is being able to extract and reuse logic between components without introducing extra nesting into the tree.
The fact that this design also eliminates the need for class gotchas is a consequence, not the main motivation.” — Dan Abramov
“With hooks we separate code not based on the lifecycle method name but based on what the code is doing” — Dan Abramov
Please do not hesitate to give us your thoughts about React Hooks.
Sources:
- https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889
- https://www.richardkotze.com/coding/hoc-vs-render-props-react
- https://reactjs.org/docs/higher-order-components.html
- https://reactjs.org/docs/render-props.html
- https://medium.com/@Zwenza/functional-vs-class-components-in-react-231e3fbd7108
- https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc
- https://www.pluralsight.com/courses/using-react-hooks
…
Up to 50 claps per reader · 120 on Medium