Hooks are all the rage in React, especially now that they are stable as of React v16.8! The Hooks Proposal is an attempt to address several major concerns developers have with React. Essentially, a Hook is a special function that allows you to “hook into” React features. Hooks are ideal if you’ve previously written a functional component and realized that you need to add state to it.
If you’re new to Hooks and would like an overview, check out our introduction to React Hooks.
In this tutorial, we’re going to take a previously written class-based component and convert it into a functional component using the useState
Hook.
🐊 Alligator.io recommends ⤵
Fullstack Advanced React & GraphQL by Wes Bos
ⓘ About this affiliate link
useState Overview
Simply put, useState
declares a state variable to preserve values between function calls. The variables are preserved by React. useState
only takes one argument that initializes the value of the state that you’re setting. By implementing useState
we no longer need this.whatever
, we can access the variable directly.
Getting Started
To help speed things along, we’ve prepared some starter code. In the starter code, we installed the latest version of react
and react-dom
as well as reactstrap to help us have some easy formatting.
To get started:
$ git clone https://github.com/alligatorio/convert-class-to-hook $ npm i $ npm start
Class-Based Component
First let’s take a look at the current ClassBasedForm.js
component located in the components folder. If you work with React frequently, this should all look pretty standard. Now let’s convert it to a functional component using the useState
Hook.
Converting to a Function
First, create an additional Form
component in the components
folder. Title it FunctionBasedForm.js
. We’ll use this component to build an identical form using the useState
Hook. Having the two components side by side will allow you to toggle and easily identify differences.
We’ll first import React and create a simple function variable called FunctionBasedForm
that returns some text. Be sure to export this function.
FunctionBasedForm.js
import React from 'react'; const FunctionBasedForm = () => { return ( <h1>Function Based Form</h1> ) }; export default FunctionBasedForm;
Flip over to your App.js
file and import your new component. For now, just comment out the previous ClassBasedForm
import. Replace your previous component with your new <FunctionBasedForm />
component in the return statement.
App.js
import React, { Component } from 'react'; import { Container } from 'reactstrap'; // import ClassBasedForm from './components/ClassBasedForm'; import FunctionBasedForm from './components/FunctionBasedForm'; import Logo from './assets/alligator-logo2.svg'; import './App.css'; class App extends Component { render() { return ( <div className="App"> <img src={ Logo } alt="alligator.io logo" width="200" /> <Container> <FunctionBasedForm /> </Container> </div> ); } } export default App;
Let's take a look at localhost:3000 in the browser to be sure we don't see any errors.
Declaring State
React has made some pretty powerful updates lately, here's a progression of setting state, just in case you've been hiding under a rock... or out climbing some! 🧗
In our original ClassBasedForm.js
component we initialize our state using a constructor. We no longer need to initialize state in a typical class component, nor do we for hooks! Let’s look at an example.
Most of us are familiar with initializing state like so:
constructor(props) { super(props); this.state = { email: '', password: '', }; }
Since React 16, we no longer need a constructor. The former becomes:
state = { email: '', password: '', };
Now in a functional component, we can use hooks to up our game. We can initialize state and the related setter at the beginning of our function. This could potentially eliminate several lines of code for us. Here’s how we would initialize state variables in our new component.
const [email, setEmail] = useState(''); const [password, setPassword] = useState('');
What exactly are we doing here?
Let’s break down one of the lines above.
const [ email, setEmail] = useState('');
- const: Creates a variable for both our state and the associated state variable setter.
- email: Initializes and declares our first variable email.
- setEmail: Initializes the setter function associated with the variable email. Though it may seem redundant
useState
is only intended to be used for a single value. - useState(‘’): Declares that the variable email starts as an empty string.
Implementing the related variable function
Let’s jump back into our code and take the code we had from the class-based component and strip out all the functions, click handlers and state variables. Next add { useState }
to your React import as well as the lines above where we initialized our variables and associated setters. Your code should look something like this:
import React, { useState } from 'react'; import { Form, FormGroup, Input, Label, Col, Button, } from 'reactstrap'; const FunctionBasedForm = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); return ( <Form> <h1>Function Based Form</h1> <FormGroup row> <Label for="exampleEmail" sm={ 2 }>Email</Label> <Col sm={ 8 }> <Input type="email" name="email" id="exampleEmail" placeholder="email" /> </Col> </FormGroup> <FormGroup row> <Label for="examplePassword" sm={ 2 }>Password</Label> <Col sm={ 8 }> <Input type="password" name="password" id="examplePassword" placeholder="password" /> </Col> </FormGroup> <FormGroup check row> <Col sm={ { size: 10, offset: 8 } }> <Button>Submit</Button> </Col> </FormGroup> </Form> ) }; export default FunctionBasedForm;
If you jump back to your browser, the app should look the same as it did when you ran npm start
.
Adding Functionality Back
Now let’s revise our functions to utilize Hooks.
Let’s take a look at how we updated state in our class-based component:
onChange={ (event) => this.setState({ email: event.target.value })
With hooks, we no longer need this
or this.setState()
since we’re already initiating our state variables and attaching a setter. Since we only have two variables we’re using, we’re going to use an inline function to call the setter that we initiated in useState
for each input. We’ll also add our value back without the this prefix.
<Input type="email" name="email" id="exampleEmail" placeholder="email" value={ email } onChange={ event => setEmail(event.target.value) } />
<Input type="password" name="password" id="examplePassword" placeholder="password" value={ password } onChange={ event => setPassword(event.target.value) } />
🐊 If we had several variables and wanted to share them between components, we would use an additional hook that we'll cover in a later article.
Now let’s rewrite our handleSubmit
function.
Here’s how the function was previously written:
handleSubmit(e) { e.preventDefault(); console.log(this.state); }
Tip! In React 16, if you write this as an arrow function, you won't need to bind it in the constructor.
We now need to create a const
for the function. We again prevent the default functionality, set the variables, and console.log
them.
const handleSubmit = e => { e.preventDefault(); console.log(email); console.log(password); }
Now we can add our handleSubmit
function to the onSubmit
in our form.
Here’s how your new functional hook should look:
import React, { useState } from 'react' import { Form, FormGroup, Input, Label, Col, Button, } from 'reactstrap'; const FunctionBasedForm = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = event => { event.preventDefault(); console.log(email); console.log(password); } return ( <Form onSubmit={ handleSubmit }> <h1>Function Based Form</h1> <FormGroup row> <Label for="exampleEmail" sm={ 2 }>Email</Label> <Col sm={ 8 }> <Input type="email" name="email" id="exampleEmail" placeholder="email" value={ email } onChange={ event => setEmail(event.target.value) } /> </Col> </FormGroup> <FormGroup row> <Label for="examplePassword" sm={ 2 }>Password</Label> <Col sm={ 8 }> <Input type="password" name="password" id="examplePassword" placeholder="password" value={ password } onChange={ event => setPassword(event.target.value) } /> </Col> </FormGroup> <FormGroup check row> <Col sm={ { size: 10, offset: 8 } }> <Button>Submit</Button> </Col> </FormGroup> </Form> ) }; export default FunctionBasedForm;
Flip back over to your browser and add some values to your form and hit submit. If your app console.log()
s came back with the variables you entered, you’re golden! Well done! 👍
👉 useState is one of many Hooks we'll be covering, so stay tuned!