DEV Community

Liz Laffitte
Liz Laffitte

Posted on

Converting a React Class Component with Connect to a Functional Component using React Hooks

First things first, make sure that you read the React Hook docs. 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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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("")

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

What does this mean for our Routes component? FREEDOM!
FREEDOM

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
  })
Enter fullscreen mode Exit fullscreen mode

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?! :)

Top comments (0)