React.Component
If you have worked with React, you should already know that components are the building blocks of a React application. Since the React v0.13.0 release, components allow us to split the UI into reusable and independent pieces using ES6 classes.
"I already know that. React is awesome!"
import React from 'react';
import AnotherCompoment from './components/AnotherComponent';
// A React class component
export default class MyComponent extends React.Component {
contructor(props) {
super(props);
this.state = {
...
};
}
render() {
<div>
...
<AnotherComponent />
...
</div>
}
};
Function Component
Okay! Later with the React v0.14.0 release, the React team has introduced another way to create components, named function components. Using an ES6 arrow (or a simple) function, we could now create a stateless React component.
"I told you. React is awesome! I use function components a lot."
Those function components have no state, no lifecycle methods, but they are easy to write. The amount of code you need to write is quite small compared to a class component.
import React from 'react';
export default const MyComponent = ({ name = "" }) => {
return <div>Hello {name}!</div>
};
This is a great fit for components that just need to render some UI.
However, if our component needs a state or has to use a React lifecycle method, we had to use a class. That sucks, right? What if we want to benefit from the React state and lifecycle concepts, without having to use a class 🧐? Anyone?
"Uh... Let me think about it..."
React Hooks
Again, the React team came to the rescue with the React v16.8 release. They have introduced the BEST concept to the React library so far: React Hooks 🔥🚀!
"But wait... what is it?"
What? You haven't heard about React hooks yet? That's okay, let's dive into it together.
In a nutshell, React hooks are functions that allow us to use state and other React features inside our function components. No class needed! Isn't that wonderful?
"A function? This is what you call the BEST React concept?"
Think about it. You can now write simple and readable function components and still use React concepts. No more class creation. No more constructor. No more binding. No more duplicate logic. No more hassle when sharing non-visual logic between components. No more...
"Wait. Wait. That's too much for me. I need some examples."
I got it. Let's imagine we'd like to build a login form so that our users can authenticate into our app by using their email and password. Let see how to build the login form UI only using a function component and React hooks.
For simplicity, we won't dive into the authentication logic which is a completely different topic.
Login Form with React Hooks
Alright. First, we need to create our function component. Easy, right?
import React from 'react';
export default const LoginForm = (props) => {
return (
<div>
<h1>Login Form</h1>
</div>
);
}
Now, let's create the form itself by writing some JSX for the input fields and the submit button.
<form>
<label>
Email Address:
<input type="text" />
</label>
<label>
Password:
<input type="password" />
</label>
<input type="submit" value="Submit" />
</form>
In order to handle the submission of the form and to have access to the data that the user enters into it, we must convert our form component to a controlled component. That's something we are used to with class components and the React state. However, since we’re no longer using a class, we need a new way to add and manage the state inside our function component.
"You talked about React hooks. I believe there is a hook for that, right?"
State Hook - useState
Exactly, as of React v16.8.0, React gives us the ability to manage state inside function components via the useState method.
import React, { useState } from 'react';
The useState method, like any other React hooks, must be used inside a function component. useState takes in a single argument, the initial value for the state. It returns an array with the first item being the stateful value and the second item being a function to update that state.
In our case, we need 2 state values for handling user inputs. One for the email address, and one for the password. Let's initiate those ones with an empty string.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
We'll use the states and the update functions to control the form inputs.
...
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
...
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
...
The setEmail and the setPassword functions are used to update each corresponding state. They accept a new state value and enqueues a re-render of the component.
"Waw! That's amazing."
We now have a controlled component built with a simple function and the React useState hook. We can finally create a function to handle the form submission as usual.
import React, { useState } from 'react';
export default const LoginForm = (props) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
// Authenticate user
...
};
return (
<div>
<h1>Login Form</h1>
<form onSubmit={handleSubmit}>
...
</form>
</div>
);
}
"One more thing. How do I put the focus on the email input? I don't have access to the componentDidMount method anymore, do I?"
Effect Hook - useEffect
Good question. We do not have access to the componentDidMount, componentDidUpdate, and componentWillUnmount methods inside our function components. But, we now have the Effect hook, useEffect. This hook allows us to perform "side effects" from a function component.
import React, { useEffect } from 'react';
In other words, this is where we'll handle things like data fetching, setting up subscriptions, or manual DOM manipulation. The useEffect accepts a function that will be run by React after every render.
useEffect(() => {
...
})
Let's come back to our example. Thanks to the useEffect hook we can set the focus to our email input field when our component renders. For doing that, we also need a way to access the DOM node. And guess what? React has a hook to do that, useRef.
import React, { useRef } from 'react';
"Wait! I thought we already have a way to access DOM nodes with React.createRef(). What's the difference with the useRef hook?"
Ref Hook - useRef
You are right. To access a DOM node in a class component, we would use the React.createRef method. However, this method always creates a new ref on every render. To overcome that, we would typically put the ref in an instance property inside the class constructor. But we do not have this option in a function component.
That's why we need to use the useRef hook in a function component. Indeed, useRef will return the same ref each time for the full lifetime of the component.
"Okay. I got it. Can you please show me how to use it?"
export default const LoginForm = (props) => {
...
const emailEl = useRef(null);
...
return (
...
<input
ref={emailEl}
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
...
);
Now that we have access to the DOM node of our email input, let's put the focus on it using the Effect hook.
useEffect(() => {
emailEl.current.focus();
});
Skipping Effects
I told you before that React will run our effect function after every render by default. This is the case with our current implementation. So, every time the user will enter a value in one of the input fields, the state will be updated, and the component will be rendered. Then, our effect function will be run and the focus will be put on the email input again. That's not what we want.
Fortunately, we can customize this behavior by passing a second argument to the useEffect hook, a dependencies array. In our case, we can pass an empty array to tell React that it never needs to re-run our effect function.
useEffect(() => {
emailEl.current.focus();
}, []);
That's it folks! We have a login form built with a functional component and React hooks only. Here's the full source code in case you need it:
import React, { useState, useEffect, useRef } from 'react';
export default const LoginForm = (props) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const emailEl = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// Authenticate user
...
};
useEffect(() => {
emailEl.current.focus();
}, []);
return (
<div>
<h1>Login Form</h1>
<form onSubmit={handleSubmit}>
<label>
Email Address:
<input
ref={emailEl}
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
Password:
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<input type="submit" value="Submit" />
</form>
</div>
);
}