Rails React Redux Thunk User Auth with Cookies and Sessions — Part II

Luciana
5 min readDec 10, 2021
Unlocking the user authentication :D

On this second part, I’ll go over how to set up the React Redux Thunk frontend user authorization using cookies and sessions. The Rails backend is on Part I.

As already mentioned on Part I, I followed the edutechional video playlist and made some changes to meet the React Redux Thunk requirements.

Inside the package.json add "proxy": “http://localhost:3000" the initial localhost portal that you’re using on the backend, in my case, I use the localhost:3000 on backend and localhost:3001 on frontend.

After you connected Thunk with your store, added it on your react-redux Provider, it’s time to create the reducers, actions, forms, and connect everything together.

I also created a separated folder called “constants.js” with all the actions types. (I don’t have a SIGNUP one because the reducer and action are the same so I reuse LOGIN on signup).

export const LOGIN = "LOGIN";
export const LOGOUT = "LOGOUT";

Inside the reducer.js file, I separate each reducer and joined them with combineReducers from redux. On this example, I’ll be showing just the userReducer only.

import { LOGIN, LOGOUT } from '../actions/constants';function usersReducer(state = {}, action) {
switch (action.type) {
case LOGIN:
return action.payload
case LOGOUT:
return {}
default:
return state
}
}

Inside the userActions.js add the fetch and dispatch functions. Signup is to create a new user, login is to login an existing user, logout to logout a user, and loginStatus to get the current user and check if there’s a user logged in or not.

import { LOGIN, LOGOUT } from "./constants";export function signup(user) {
return dispatch => {
fetch("/users", {
method: "POST",
headers: {
"Access-Control-Allow-Origins": "*",
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(user)
},
{ withCredentials: true }
)
.then(resp => resp.json())
.then(data => {
if (data.status === "create") {
dispatch({ type: LOGIN, payload: data })
}
})
.catch(error => console.log("registration error", error))
}
}
export function login(user) {
return dispatch => {
fetch("/sessions", {
method: "POST",
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(user)
},
{ withCredentials: true }
)
.then(resp => resp.json())
.then(data => {
if (data.logged_in) {
dispatch({ type: LOGIN, payload: data })
} else {
throw new Error()
}
}).catch(error => console.log("login error", error))
}
}
export function logout() {
return dispatch => {
fetch("/logout", {
method: "DELETE"
}, { withCredentials: true })
.then(resp => resp.json())
.then(data => {
if (!data.logged_in) {
dispatch({ type: LOGOUT, payload: data })
}
})
.catch(error => console.log("logout error", error))
}
}
export function loginStatus() {
return dispatch => {
fetch("/logged_in", { withCredentials: true })
.then(resp => resp.json())
.then(data => {
if (data.logged_in) {
dispatch({ type: LOGIN, payload: data })
} else {
dispatch({ type: LOGOUT, payload: data })
}
})
}
}

Inside the “Signup.js” file, there’s the user form. After the user submit the form, it uses the connect from ‘react-redux’ to connect it to the dispatch on our actions and reducers and fetch to the backend.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { signup } from '../../actions/usersActions';
import { Link } from 'react-router-dom';
import '../../style/Login.css';
class Signup extends Component {
state = {
first_name: "",
last_name: "",
email: "",
password: "",
about: "",
image: ""
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
handleSubmit = e => {
e.preventDefault()
this.props.dispatchSignup(this.state)
this.props.routerProps.history.push("/companies")
}
render() {
return (
<div className="signup-main-container">
<div className="signup-container">
<h3>SIGN UP</h3>
<hr />
<form onSubmit={this.handleSubmit}>
<label>First Name</label>
<input type="text" name="first_name" value={this.state.first_name} onChange={this.handleChange} placeholder="John" required />
<label>Last Name</label>
<input type="text" name="last_name" value={this.state.last_name} onChange={this.handleChange} placeholder="Doe" required />
<label>Email</label>
<input type="email" name="email" value={this.state.email} onChange={this.handleChange} placeholder="johndoe@johndoe.com" required />
<label>Password</label>
<input type="password" name="password" value={this.state.password} onChange={this.handleChange} required />
<label>About</label>
<input type="text" name="about" value={this.state.about} onChange={this.handleChange} placeholder="Social media fan. Proud entrepreneur. Pop culture expert." />
<label>Image</label>
<input type="text" name="image" value={this.state.image} onChange={this.handleChange} placeholder="http://myimage.com" />
<input type="submit" value="Create Account" id="login-btn" />
</form>
<p id="login-signup-p-tag">Already have an account? <Link to="/signin">Sign in</Link></p>
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
dispatchSignup: user => dispatch(signup(user))
}
}
export default connect(null, mapDispatchToProps)(Signup);

The similar happens on the Login process.

import React, { Component } from 'react';
import { login } from '../../actions/usersActions';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import '../../style/Login.css';
class Login extends Component {
state = {
email: "",
password: "",
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
handleSubmit = e => {
e.preventDefault()
this.props.dispatchLogin(this.state)
this.props.routerProps.history.push("/companies")
}render() {
return (
<div className="login-container">
<h3>SIGN IN</h3>
<hr />
<form onSubmit={this.handleSubmit}>
<label>Email</label>
<input type="email" name="email" value={this.state.email} onChange={this.handleChange} placeholder="johndoe@johndoe.com" required />
<label>Password</label>
<input type="password" name="password" value={this.state.password} onChange={this.handleChange} required />
<input type="submit" value="Login" id="login-btn" />
</form>
<p id="login-signup-p-tag">Don't have an account yet? <Link to="/signup">Sign up</Link></p>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
dispatchLogin: user => dispatch(login(user))
}
}
export default connect(null, mapDispatchToProps)(Login);

The logout button is only the Navbar of my application so I added the logout dispatch inside the Navbar.js. If the user is logged_in, I show the logout button otherwise it shows the sign in link (the signup is inside the sign in).

import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import '.././style/Navbar.css';
import logo from '.././style/logo.png';
import { logout } from '../actions/usersActions';
import { connect } from 'react-redux';
class Navbar extends Component {
state = {
user: {}
}
render() {
return (
<div className="navbar">
<nav>
<ul>
{this.props.userLoggedIn.logged_in ?
<li id="navbar-li-logout"><button onClick={() => this.props.logout(this.state)} id="navbar-logout-btn">LOGOUT</button></li>
: <li id="navbar-li-signin"><NavLink exact to="/signin">SIGN IN</NavLink></li>
}
</ul>
</nav>
</div>
)
}
}

To check if the user is logged_in and who is the current user, I added the loginStatus inside the App.js, so I can get this information every time the component is mount and I can pass this information to the Navbar (where it can pass this prop to the other components) and to the main component, which is called CompaniesContainer in this case.

import React, { Component } from 'react';
import Navbar from './components/Navbar';
import CompaniesContainer from './containers/CompaniesContainer';
import { loginStatus } from './actions/usersActions';
import { connect } from 'react-redux';
class App extends Component {componentDidMount() {
this.props.loginStatus()
}
render() {
return (
<div className="App">
<Navbar userLoggedIn={this.props.users ? this.props.users : null} />
<CompaniesContainer userLoggedIn={this.props.users ? this.props.users : null} />
</div>
);
}
}
const mapStateToProps = state => {
return {
users: state.users
}
}
const mapDispatchToProps = dispatch => {
return {
loginStatus: () => dispatch(loginStatus())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

A bug that happened to me multiple times — not only on the user auth implementation but on my project overall — was the data was being persistent on the backend but it was only showing on the page after I hard refresh it. If you face this issue, it’s probably an issue on your reducer return. Add a debugger there, before the return, and play around with it until you find the exactly desire return.

For reference, here are my project Github backend and frontend.

P.S. I also followed the edutechional video playlist steps before I implemented it on my project to make sure it would work. Here are the Github backend and frontend (not using Redux/Thunk).

--

--