Converting a React Class Component with Connect to a Functional Component using React Hooks
Tags: react, hooks, redux, refactorit
Published: 2020-12-30 02:07:52

First things first, make sure that you read the [React Hook docs](https://reactjs.org/docs/hooks-intro.html). This post is not a replacement for the docs, nor will it be a deep dive into React hooks themselves. 

The React docs are fairly easy to read and understand conceptually. But if you’re anything like me, nothing beats seeing an actual example in the wild.

We’re going to be going over converting two class components (a higher-order Routes component and a  login form) into functional components that use React hooks.

> Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
 -- React Docs

First we have a higher-order component that does the work of connecting to the Redux store, mapping dispatch and state to props and passing these props down to child components. Right now, it just has a few child components, but if I were to continue adding routes and passing props, it would continue to get bulky and hard to read. 

```
import React, {Component} from "react";
import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom";
import Login from '../components/Login'
import Home from '../components/Home'
import Signup from '../components/Signup'
import {signup, login} from '../actions/userActions'
import {connect} from 'react-redux'



class Routes extends Component {
  loggedIn = () =>{
    return this.props.currentUser
}

  render(){
    const {login, signup} = this.props
    return (
      <>
      <Router>
        <Switch>
          <Route path="/" exact component={Home}/>
          <Route exact path='/login' >
            {this.loggedIn() ? <Redirect to="/" /> : <Login login={login}  />}
          </Route>
          <Route exact path='/signup' >
            {this.loggedIn() ? <Redirect to="/" /> : <Signup signup={signup}  />}
          </Route>
         
        </Switch>
      </Router>
      </>
    )
  }
}
const mapStateToProps = ({currentUser}) => ({currentUser})
export default connect(mapStateToProps, {signup, login})(Routes)
```
As you can see, we're importing the Login component into the Routes class component, and passing it a dispatch action, which has been mapped to a prop. 

This is the original Login component:
```
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'
class Login extends Component {
    state = {
        username: "",
        password: ""
    }
    handleOnChange = (e) => {
        this.setState({
            [e.target.name]: e.target.value
        })
    }
    handleOnSubmit = (e) => {
        e.preventDefault()
        this.props.login({this.state, token})
        this.props.history.push('/listings')
    }
    render() {
        const {username, password} = this.state
const token = document.querySelector('meta[name="csrf-token"]').content;
        return(
            <form id="login-form" onSubmit={this.handleOnSubmit}>
                <h1>Login</h1>
                <label>Username: </label><br />
                <input type="text" value={username} onChange={this.handleOnChange} name="username" /><br /><br />
                <label>Password: </label> <br />
                <input type="password" value={password} name="password" onChange={this.handleOnChange} />
                <br /><br />
                <input type="submit" value="Log In"  />
            </form>
        )
    }
}
export default withRouter(Login)
```
Let's start by changing how we handle state in this component with the useState() hook. We'll also need to remove the class keyword.

```
import React, { useState} from "react"

function Login() {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")

    const handleOnSubmit = (e) => {
        e.preventDefault()
    }
    const token = document.querySelector('meta[name="csrf-token"]').content;
    return(
        <form onSubmit={handleOnSubmit}>
            <h1>Log in</h1>
            <label>Username: </label><br />
            <input type="text" value={username} onChange={e => setUsername(e.target.value)} name="username" /><br /><br />
            <label>Password: </label> <br />
            <input type="password" value={password} name="password" onChange={e => setPassword(e.target.value)} />
            <br /><br />
            <input type="submit" value="Log in"  />
        </form>
    )
}

export default Login
```
We've changed `class Login extends Component {` to `function logion(){`; gotten rid of the render() wrapper around our return statement; and replaced the state declaration. 

We've also imported useState(). To declare state variables, we add the following lines:
```
 const [username, setUsername] = useState("")
 const [password, setPassword] = useState("")

```
These lines will declare the state variables (`username` and `password`), create functions that will update each of our variables respectively (`setUsername` and `setPassword`), and gives our variables their initial values (Empty strings `""` in our case).

Take a look at our inputs' onChange handlers. Instead of changing state by calling `this.setState()`, now we use our newly created functions, `setUsername` and `setPassword`. We can get rid of our handleOnChange function entirely.

```
 <form onSubmit={handleOnSubmit}>
            <h1>Log in</h1>
            <label>Username: </label><br />
            <input type="text" value={username} onChange={e => setUsername(e.target.value)} name="username" /><br /><br />
            <label>Password: </label> <br />
            <input type="password" value={password} name="password" onChange={e => setPassword(e.target.value)} />
            <br /><br />
            <input type="submit" value="Log in"  />
        </form>
```
Now, what about dispatching our login action? For that, we'll use the useDispatch hook, provided by react-redux. We'll also need to import our action, directly into the Login component. 

```
import React, { useState} from "react"
import { useDispatch } from 'react-redux'
import {login} from '../actions/userActions'

function Login() {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")
    const dispatch = useDispatch()
    
    const handleOnSubmit = (e) => {
        e.preventDefault()
        dispatch(login({username, password}, token))
    }
    const token = document.querySelector('meta[name="csrf-token"]').content;
    return(
        <form onSubmit={handleOnSubmit}>
            <h1>Log in</h1>
            <label>Username: </label><br />
            <input type="text" value={username} onChange={e => setUsername(e.target.value)} name="username" /><br /><br />
            <label>Password: </label> <br />
            <input type="password" value={password} name="password" onChange={e => setPassword(e.target.value)} />
            <br /><br />
            <input type="submit" value="Log in"  />
        </form>
    )
}

export default Login
```
We've added our import statements, saved the hook `useDispatch` into a variable, and added one important line to handleOnSubmit:  dispatch(login({username, password}, token))

We're **dispatching** the **login** action, and passing our action the payload it needs. 

We'll also import the useHistory hook from react-router-dom and the useCookies hook from react-cookie to keep our submit functionality.

```
import React, { useState} from "react"
import { useHistory } from 'react-router-dom'
import { useDispatch } from 'react-redux'
import { useCookies } from 'react-cookie'
import {login} from '../actions/userActions'

function Login() {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")
    const history = useHistory()
    const [setCookie] = useCookies(['user'])
    const dispatch = useDispatch()
    
    const handleOnSubmit = (e) => {
        e.preventDefault()
        setCookie('user', username, {path: '/'})
        dispatch(login({username, password}, token))
       history.push('/')
    }
    const token = document.querySelector('meta[name="csrf-token"]').content;
    return(
        <form onSubmit={handleOnSubmit}>
            <h1>Log in</h1>
            <label>Username: </label><br />
            <input type="text" value={username} onChange={e => setUsername(e.target.value)} name="username" /><br /><br />
            <label>Password: </label> <br />
            <input type="password" value={password} name="password" onChange={e => setPassword(e.target.value)} />
            <br /><br />
            <input type="submit" value="Log in"  />
        </form>
    )
}

export default Login
``` 

What does this mean for our Routes component? FREEDOM!
![FREEDOM](https://media.giphy.com/media/6901DbEbbm4o0/giphy.gif)

As a reminder, here's our original Routes component:
```
import React, {Component} from "react";
import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom";
import Login from '../components/Login'
import Home from '../components/Home'
import Signup from '../components/Signup'
import {signup, login} from '../actions/userActions'
import {connect} from 'react-redux'



class Routes extends Component {
  loggedIn = () =>{
    return this.props.currentUser
}

  render(){
    const {login, signup} = this.props
    return (
      <>
      <Router>
        <Switch>
          <Route path="/" exact component={Home}/>
          <Route exact path='/login' >
            {this.loggedIn() ? <Redirect to="/" /> : <Login login={login}  />}
          </Route>
          <Route exact path='/signup' >
            {this.loggedIn() ? <Redirect to="/" /> : <Signup signup={signup}  />}
          </Route>
         
        </Switch>
      </Router>
      </>
    )
  }
}
const mapStateToProps = ({currentUser}) => ({currentUser})
export default connect(mapStateToProps, {signup, login})(Routes)
```
Gross. What if we replace the bulk with hooks?

No more class, no more connect, no more mapStateToProps, no more mapDispatchToProps, no more importing actions that have to be passed as props!

We're going to use two additional hooks: useSelector() to get data from the Redux store and useEffect() in place of our usual componentDidMount to perform a side effect operation. 

```
import React, {useEffect} from "react";
import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom";
import Login from '../components/Login'
import Home from '../components/Home'
import Signup from '../components/Signup'
import {useSelector, useDispatch} from 'react-redux'
import { useCookies } from 'react-cookie';
import { setCurrentUser } from "../actions/userActions";


function Routes() {
  const loggedInUser = useSelector(state => state.currentUser)
  const [cookies] = useCookies(['user']);
  const dispatch = useDispatch()
  useEffect(() => {
    cookies["user"] ? dispatch(setCurrentUser(cookies["user"])) : false
  })
 
    return (
      <>
      <Router>
        <Switch>
          <Route path="/" exact component={Home}/>
          <Route exact path='/login' >
            {loggedInUser ? <Redirect to="/" /> : <Login />}
          </Route>
          <Route exact path='/signup' >
            {loggedInUser ? <Redirect to="/" /> : <Signup  />}
          </Route>
         
        </Switch>
      </Router>
      </>
    )
}


export default Routes
```

Did you see that beautiful export line?! Did you see that we don't have to pass anything to the other components?

Instead of:
```
  loggedIn = () =>{
    return this.props.currentUser
}
``` 

We have: ` const loggedInUser = useSelector(state => state.currentUser)`

We've created a new variable `loggedInUser` by access the redux store directly, asking for the currentUser.

We also use useEffect(), useCookies() and useDispatch() to see if we have a user cookie, and dispatch the setCurrentUser action if not. 

```
  useEffect(() => {
    cookies["user"] ? dispatch(setCurrentUser(cookies["user"])) : false
  })
```

Ta-da! Hooks can seem intimidating if you've learned React without them, but they're not hard to implement in pieces. I'll be starting with my youngest/inner-most components and working my way out.

Questions? Comments? Smart remarks?! :)