My React Hook Notes
I have been doing a lot of reading on React Hook, there's quite the hype for it. The more I read, the more I understood why. It will breakdown existing complex app logic into simpler, flatter architecture. There were some key points I wrote down as part of my reading, which I think will be very helpful later to refresh my memories and decided to upload it on here for others to see as well. It is a digested version of the reading materials I listed in the source section below.
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
No breaking changes
- Completely opt-in. You can try Hooks in a few components without rewriting any existing code. But you don’t have to learn or use Hooks right now if you don’t want to.
- 100% backwards-compatible. Hooks don’t contain any breaking changes.
- Available now. Hooks are now available with the release of v16.8.0.
- No plan of removing classes
- Hooks builds on top of other React concepts - props, state, context, refs, and lifecycle
Must remember
Hooks are JavaScript functions, but they impose two additional rules:
- Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
- Only call Hooks from React function components. Don’t call Hooks from regular JavaScript functions. (There is just one other valid place to call Hooks — your custom Hooks. We’ll learn about them in a moment.)
Use the linter plugin to enforce these rules automatically.
React Hook V.S. Traditional React Class
There are many reasons to use Hook, but the most important might to reduce complexity for both machine and people.
People
Reuse stateful logic between components in React has always been a goal and a challenge. We can achieve it with render props and higher-order components. But these patterns require you to restructure your components when you use them, which can be cumbersome and make code harder to follow. If you look at a typical React application in React DevTools, you will likely find a “wrapper hell” of components surrounded by layers of providers, consumers, higher-order components, render props, and other abstractions.
Machines
Class components can encourage unintentional patterns that make these optimisations fall back to a slower path. Classes present issues for today’s tools, too. For example, classes don’t minify very well, and they make hot reloading flaky and unreliable.
// Component with Hook
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
// React Class Component
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
Hook APIs
useState
Call it inside a function component to add some local state to it. React will preserve this state between re-renders.
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect
You’ve likely performed data fetching, subscriptions, or manually changing the DOM from React components before. We call these operations “side effects” (or “effects” for short) because they can affect other components and can’t be done during rendering.
This hook adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
The ChatAPI.subscribeToFriendStatus
line is the effect that subscribes to a friend’s online status.
The return
inside useEffect
would clean up and unsubscribe from our ChatAPI when the component unmount, as well as before re-running the effect due to a subsequent render.
What does useEffect
do? By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates. In this effect, we set the document title, but we could also perform data fetching or call some other imperative API.
Why is useEffect
called inside a component? Placing useEffect
inside the component lets us access the count
state variable (or any props) right from the effect. We don’t need a special API to read it — it’s already in the function scope. Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.
Does useEffect
run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
Other Tips
- Use Multiple Effects to Separate Concerns
- Optimising Performance by Skipping Effects
useContext
Accepts a context object (the value returned from React.createContext
) and returns the current context value for that context. The current context value is determined by the value
prop of the nearest <MyContext.Provider>
above the calling component in the tree.
When the nearest <MyContext.Provider>
above the component updates, this Hook will trigger a rerender with the latest context value
passed to that MyContext
provider.
const value = useContext(MyContext);
A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimise it by using memoization.
Tip: Don’t forget that the argument to useContext must be the context object itself.
Other Hooks
Custom Hooks
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
Let's extract the logic into a custom Hook called useFriendStatus.
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
Then it can be used in different components:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
Other Tips
- Pass Information Between Hooks
- Optimising Performance by Skipping Effects