This page is a work in progress.You can help improve it. →

Well, hello there hooks!

React Hooks

We have already seen how React operates using class based components. In this lesson, we will talk about another approach for creating React components. This approach is often called hooks

History

Prior to the introduction of hooks React had the concept of stateless functional components. These are components that do not maintain an internal state with this.state and the corresponding this.setState. The simplest functional component might be our HelloWorld component:

class HelloWorld extends React.Component {
render() {
return <div>Hello, World!</div>
}
}
// <HelloWorld />

In this case, the component does not maintain any state. Each time it is rendered, it merely uses <div>Hello, World!</div> as its content. In this way, we say that it is stateless.

Stateless Functional Components

However, there is overhead in writing a component this simple as an entire class. We only need and use the render method. In these cases, we can reduce this code to just a function with the name HelloWorld

function HelloWorld() {
return <div>Hello, World!</div>
}
// <HelloWorld />

This code is already easier to read and understand. The component HelloWorld just returns the JSX representing <div>Hello, World</div>

Props for Stateless Functional Components

What about a component that receives properties from a parent component? Without a class, how do we utilize the this.props we get from inheriting from React.Component?

The answer is quite simple; the functional component receives the properties via an argument we will call props

function HelloWorld(props) {
return <div>Hello, {props.name}!</div>
}
// <HelloWorld name="Sandy" />

Again, very simple. We do not need to worry about remembering this.props or dealing with any of the extra complications of a class!

What about methods to handle events? Again the answer is quite simple. We can declare the functions either inside or outside the component! Remember, functions are just variables themselves.

Handling events in Stateless Functional Components

function handleClickOnDiv(event) {
console.log('You clicked on the div!')
}
function HelloWorld(props) {
return <div onClick={handleClickOnDiv}>Hello, {props.name}!</div>
}
// <HelloWorld name="Sandy" />

We can also define the function inside the method to add some "encapsulation." Our handle methods cannot name collide with functions that other components might need.

function HelloWorld(props) {
function handleClickOnDiv(event) {
console.log('You clicked on the div!')
}
return <div onClick={handleClickOnDiv}>Hello, {props.name}!</div>
}
// <HelloWorld name="Sandy" />

Finally, we may choose to write the handling functions as arrow functions. Given that we do not need the ability of arrow function to automatically bind this, we don't need the arrow syntax. However, some developers prefer to write handling functions as arrow syntax.

function HelloWorld(props) {
const handleClickOnDiv = event => {
console.log('You clicked on the div!')
}
return <div onClick={handleClickOnDiv}>Hello, {props.name}!</div>
}
// <HelloWorld name="Sandy" />

Functional Components vs Stateful Components

Ok, so these examples demonstrate that writing components as simple functions is more straightforward than writing classes. However, this style was limited to only those that do not maintain a state. This limitation would lead some developers to have one stateful component that only maintained the state and then deferred all rendering to a stateless component.

This might look like:

class ToDoListContainer extends React.Component {
state = {
list: [],
}
addItem = item => {
this.setState({ list: [...this.state.list, item] })
}
// code to remove items, sort items, mark complete, etc.
render() {
return (
<ToDoList
list={list}
addItem={this.addItem}
deleteItem={this.deleteItem}
/>
)
}
}
function ToDoList(props) {
return (
<ul>
<li onClick={props.addItem}>Add item!</li>
{list.map(item => (
<li key={item.id} onClick={props.deleteItem}>
{item}
</li>
))}
</ul>
)
}

Thus the ToDoList is still a functional component. It does not maintain a state. Also, the ToDoListContainer does not do any rendering, deferring that to the ToDoList component.

In this way, we get the best of both worlds, the stateful component only manages state, and the stateless component receives its properties from the outside world and is only concerned with rendering content and dispatching events.

Can We Do Better?

Leading up to the React 16.8.0 release, the React developers wanted to address issues noticed over five years of writing and supporting React based projects.

Here are the issues these projects would have:

  • It is hard to reuse stateful logic between components

Take our ToDoListContainer example from above. Its real purpose is to manage a list of added, deleted, sorted, and marked complete items. If we wanted to reuse this idea for a different domain, say perhaps an entire project, it would not be straightforward. It will always want to render a ToDoList. Various approaches exist to deal with this. For instance, we could send the class name of the component as one of the props to ToDoListContainer -- or we could use the fact that this.props.children would contain the children of a component. We could then return something like return this.props.children, but with a way to inject the prop of the list.

However, all of these approaches had drawbacks.

  • Complex components become hard to understand

We have seen some of the React Lifecycle when we used componentDidMount to find a place to perform tasks such as loading data into state from an API

However, many other lifecycle methods exist. On large projects, classes would become loaded with extra code to handle many different component requirements. These components often became large and difficult to refactor.

  • Classes confuse both people and machines

Classes are relatively new to JavaScript, which, until recently, did not have native support in most browsers for the syntax. The new nature of classes meant that any code with class would have to be transpiled. It also makes us have to understand JavaScripts dreaded this

functions are much simpler to write and to understand. Functions lack the syntax concept complexity of classes.

Enter Hooks

The idea of hooks were introduced to allow React developers to do everything a traditional class based component could do, but with only using function style definitions.

React 16.8 was released on February 6, 2019. So they are very new to the React landscape. However, since their introduction, they have risen in popularity and are considered the primary development style going forward. Most third-party libraries support class based components but are developing all the latest features focused on hook usage.

The Simplest Hook: useState

We said that one of the things that Stateless Functional Components could not do is maintain state. Certainly, before hooks we could not define a function style component and maintain state at all. This limitation is what useState attempts to resolve.

Let us make a component that has a straightforward counter that increments its value on pressing a button.

function Counter() {
// prettier-ignore
const counterValueAndSetMethod /* this is an array */ = useState( 0 /* initial state */)
const counter = counterValueAndSetMethod[0]
const setCounter = counterValueAndSetMethod[1]
return (
<div>
<p>The counter is {counter}</p>
<button onClick={() => setCounter(counter + 1)}>Count!</button>
</div>
)
}

Whoa! Let us break this down.

We start the very first line of code with:

const counterValueAndSetMethod = useState(0)

This line of code does a few things. First, it declares that we are going to use some state. It then says that the state's initial value is going to be the number 0.

useState rules

useState has a few particular rules that we need to remember:

  1. The value given to useState in parenthesis is used as the initial value only the first time the component's instance is rendered. Even if the component is rendered again due to a state change, the state's value isn't reset to the initial value. This behavior may seem strange if we are going to call useState again when that render happens. How React makes this happen is a concept deeper than we have time to discuss here.

  2. useState always returns an array with exactly two elements. The first element is the current value of the state and the second element is a function that can change the value of this state

In this way useState is kind of a mixture of this.state and this.setState -- since useState returns a variable containing the state, and another that allows us to update the state.

It is different than this.setState from classes in these ways:

  1. this.setState in classes allows us to do a partial update. That is we can call this.setState({ key: value}) and that key and value will be merged into the current state. However, with useState the state is replaced with whatever we send it.

  2. This is ok due to the second difference. In class we would define a single state and have to use an object to track multiple state elements in a single component. Thus you would often see states like state = { board: [], score: 0, playerOneName: 'Bill', playerTwoName: 'Susan'}. In hooks, this would be four different useState calls.

Using the useState return value

The next two lines of code:

const counter = counterValueAndSetMethod[0]
const setCounter = counterValueAndSetMethod[1]

Make two local variables to store the current value of our state, which we call counter and the method that updates the counter as setCounter

Then in the JSX, we can use those two local variables. The code <p>The counter is {counter}</p> will show the current value of the counter. The code <button onClick={() => setCounter(counter + 1)}>Count!</button> will call setCounter to change the value of the counter, and make it the counter plus one.

However, this code is not as compact as we can make it! We can use array destructuring assignment to simplify the code.

The code:

const counterValueAndSetMethod = useState(0)
const counter = counterValueAndSetMethod[0]
const setCounter = counterValueAndSetMethod[1]

can be rewritten as such:

const [counter, setCounter] = useState(0)

and is how every example of useState will appear. See this article for more details on how and why this syntax works.

Thus our component will look like this:

function Counter() {
const [counter, setCounter] = useState(0)
return (
<div>
<p>The counter is {counter}</p>
<button onClick={() => setCounter(counter + 1)}>Count!</button>
</div>
)
}

We have just combined the best of both worlds. We have the simplicity of the function component with the ability to update state!

Adding more state

Let us say we also wanted to keep track of a person's name on the counter. Traditionally we would define a state like this:

class CounterWithName extends React.Component {
state = {
counter: 0,
name: '',
}
}

However, with hooks, we will make two independent states that each track a single piece of information.

Separating these pieces of state has a few benefits:

  1. It is easier to remove one part of the state since it has its own variable and state changing function.

  2. We can more easily tell where in the code a piece of state or a state changing function is used.

  3. We don't have to worry about using this.state.name or this.state.counter, just counter and name.

Let's see what the component would look like if we were to use hooks:

function CounterWithName() {
const [counter, setCounter] = useState(0)
const [name, setName] = useState('Susan')
return (
<div>
<p>
Hi there {name} The counter is {counter}
</p>
<button onClick={() => setCounter(counter + 1)}>Count!</button>
<p>
<input
type="text"
value={name}
onChange={event => setName(event.target.value)}
/>
</p>
</div>
)
}

Demo

Hi there Susan, the counter is 0

Ah, how nice. We have independent variables to track our state, instead of chained object access (e.g. name vs this.state.name) and very simple methods to update the state setName(event.target.value)

Not everything is perfect in the land of Hooks

A complex component could have a large number of useState Furthermore, we could find ourselves tracking many variables and functions. React has another hook, useReducer, that can simplify the situation. Many third-party libraries exist to address managing complex state.

We have not talked about implementing "The first time the component is rendered, use fetch to get some data from the internet and update the current state" -- We will see that in a future lesson.

The React team has a nice example of hooks in their guide to hooks

© 2017 - 2022; Built with ♥ in St. Petersburg, Florida.