Gamers Vault (React-Redux-Rails)

Orkun Sağlam
6 min readFeb 5, 2021

Hello, world! Yes! It’s me again. Today, I will talk about the final project I did for Flatiron school. Basically, in my app, a user can browse through 500,000+ video games and add them to their collection if they want. The app allows users to sign up, log in as well. I know 500,000 + video games sound crazy. In order to do that, I decided to use RAWG API on my frontend. I knew it was going to be a challenging project but I wanted to put everything I know so far on this project and I am very happy about the results. All right, let’s dive in!.

BACKEND

I am using Rails as a backend API but I didn’t set up my rails with an API flag. Because I knew I would need some kind of authentication for login/signup. There were 2 options for me. The first one was just using JSON Web Tokens. That way, I could still set up my rails API with an API flag. But JSON Web Tokens were a bit complicated for me. I didn’t have time. I was not in a good place to learn something new and implement it in my project. It seemed not a wise decision. I will definitely look into that though when I have more time. So instead I decided to go with my old good friend session cookies.

Models and Associations

The backend implements a User model, a game model, and a joint table between the user and game called Collection.

  1. User
class User < ApplicationRecordhas_secure_password
validates :username, presence: true
validates :username, uniqueness: true
validates :username, length: { minimum: 4 }
has_many :collections
has_many :reviews
has_many :games, through: :reviews
has_many :games, through: :collections
end

2. Game

class Game < ApplicationRecordhas_many :collections 
has_many :reviews
has_many :users, through: :collections
end

3. Collection

class Collection < ApplicationRecordbelongs_to :user
belongs_to :game
end

CORS

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the server’s domain.

For this project, the config/initializers/cors.rb is configured to allow requests from http://localhost:3000 and to allow [:get, :post, :patch, :put, :delete] requests to the API. I also had to set the credentials: true this means that the server allows cookies (or other user credentials) to be included on cross-origin requests.

CONTROLLERS

Controllers are pretty straight forward. I do have sessions, games, and a users controller and they are all rendering in JSON. I didn’t use serializers honestly but that’s another good option too when rendering your data in JSON. You can easily customize the data you have with the serializers’ help.

Users Controller

Users controller is responsible for signing up, rendering the user’s collection, and removing an item from the user’s collection.

class UsersController < ApplicationControllerdef index
users = User.all
render json: users, except: [:created_at, :updated_at], include: [:collections]
end
def create
user = User.new(user_params)
if user.save
session[:id] = user.id
render json: { status: 201, user: user, logged_in: true}
else
render json: { status: 500, message: 'There was an error in creating an account'}
end
end
def collection
render json: current_user.games, except: [:created_at, :updated_at]
end
def destroy
game = Collection.find_by(game_id: params[:id])
game.destroy
render json: {message: "Succesfully deleted"}
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end

Games Controller

The game controller is responsible for creating games and adding a new game to the user’s collection.

class GamesController < ApplicationControllerdef index
games = Game.all
render json: games, except: [:created_at, :updated_at], include: [:collections]
end
def show
game = Game.find_by(id: params[:id])
render json: game
end
def create
game = Game.new(game_params)
if game.save
render json: game
end
end
def
game_collection
gameID = params[:id]
gameName = params[:name]
gameImage = params[:picture]
gameInDB = Game.find_by(api_id: gameID)
if gameInDB then
if
Collection.find_by(user_id: current_user.id, game_id: gameInDB.id)
render json: { message: "Already added!"}, status: :service_unavailable
else
added = Collection.find_or_create_by(user_id: current_user.id, game_id: gameInDB.id)
render json: added, status: :accepted
end
else
newGame = Game.find_or_create_by(api_id: gameID, picture: gameImage, name: gameName)
just_added = Collection.find_or_create_by(user_id: current_user.id, game_id: newGame.id)
if just_added
render json: just_added, status: :accepted
else
render json: { message: "Unable to add game"},
status: :service_unavailable
end
end
end
private
def game_params
params.require(:game).permit(:id, :picture, :name)
end
end

Sessions Controller

The session controller is responsible for logging in.

class SessionsController < ApplicationControllerdef create 
user = User.find_by(email: params[:user][:email])
if user && user.authenticate(params[:user][:password])
session[:id] = user.id
render json: { status: 201, user: user, logged_in: true}
else
render json: { status: 401, message: 'User not found or password incorrect'}
end
end
def logged_in
if
logged_in?
render json: { status: 201, user: current_user, logged_in: true}
else
render json: { status: 400, user: {}, logged_in: false}
end
end
def logout
reset_session
render json: { status: 200, user: {}, logged_in: false }
end
end

FRONTEND

Like I mentioned above, I am using RAWG API in order to render some data on my frontend. Setting up the API calls were pretty easy. Their documentation was really helpful. Additionally, the frontend is on React and I am using React-Redux to read data from the Redux store. I also used many React hooks in my Navbar components. I wanted to give it a try and I am very happy with the results. useDispatch, useSelector, useHistory, useState. They are all good friends of mine now.

REACT

To get up to speed with React quickly, I used the create-react-app generator to start my project. It provides a React application starter to build a single-page React app.

REDUX

React-Redux allows your React components to read data from a Redux store, and dispatch actions to the store to update data.

The <Provider /> makes the Redux store available to the rest of the app.

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import store from "./redux";
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);

The good thing is about Redux, it also provides you a connect function which allows you to connect your components to the store. In order to do that, you just need to use mapStateToProps It basically returns an object that contains the data the component needs.

import React from "react";
import BrowseCard from "../components/BrowseCard";
import { connect } from "react-redux";
import { gettingGames } from "../../../redux/actions/gameActions";
class ResultsContainer extends React.Component {componentDidMount() {
this.props.gettingGames();
}
render() {
return (
<div className="results-game-section">
{this.props.games.map((game) => {
return <BrowseCard key={game.id} gameObj={game} />;
})}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
games: state.released,
};
};
export default connect(mapStateToProps, { gettingGames })(ResultsContainer);

THUNK

It’s a middleware that used to handle asynchronous actions in Redux. By default, Redux action creators do not support asynchronous actions like fetching data, so we need to utilize Redux Thunk. Using this middleware avoids running into a problem where the action creator returns an action before the data is fetched.

const API_KEY = process.env.REACT_APP_API_KEY;
const NEW_RELEASE_URL = `https://api.rawg.io/api/games?dates=2021-01-01,2021-01-29&platforms=18,1,7?key=${API_KEY}`;
const COLLECTION_URL = `http://localhost:3001/user/collections`;
function getGames(games) {
return { type: "SET_GAMES", payload: games.results };
}
function gettingGames() {
return (dispatch) => {
fetch(NEW_RELEASE_URL)
.then((resp) => resp.json())
.then((result) => {
dispatch(getGames(result));
});
};
}
function gameSearchResults(games) {
return { type: "SEARCH_RESULTS", payload: games };
}
function getCollection(collection) {
return { type: "GET_COLLECTION", payload: collection };
}
function gettingCollection() {
return (dispatch) => {
fetch(COLLECTION_URL, {
credentials: "include",
})
.then((resp) => resp.json())
.then((data) => {
dispatch(getCollection(data));
});
};
}
export { gettingGames, gameSearchResults, gettingCollection, getCollection };

ROUTER

React Router allows single-page applications to navigate through multiple views without having to reload the entire page.

import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./components/Home/Home";
import Collection from "./components/Collection/Collection";
import Signup from "./components/Signup_Login/Signup";
import Login from "./components/Signup_Login/Login";
import Navbar from "./components/Home/Navbar";
import { checkLoggedIn } from "./redux/actions/authActions";
import { connect } from "react-redux";
import "./App.css";
import BrowseGames from "./components/BrowseGames/BrowseGames";
render() {
if (this.state.loading) return <h1>Loading...</h1>;
return (
<div className="App">
<Router>
<Navbar />
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/collection" component={Collection}></Route>
<Route path="/signup" component={Signup}></Route>
<Route path="/login" component={Login}></Route>
<Route path="/browse" component={BrowseGames}></Route>
</Switch>
</Router>
</div>
);
}

STYLING

I used FontAwesome and custom CSS for each component.

LAST WORDS

Comparing to my JavaScript project. This was more fun honestly. I think it’s because of React. It just makes everything easier. Plus, there are tons of useful packages out there. Like, I wanted to display an alert when a user adds a game to their collection and after a little search, I ran into this package called Swal. There are many packages like Swal out there. Also, React hooks. They saved me many times. I strongly recommend using all the tools out there.

--

--