DEV Community

Rachel Williams
Rachel Williams

Posted on

React / Redux Project

My final project for my program is a React / Redux application with a Rails API backend. For this project I decided to build out an application where users could login and view upcoming concerts in their area, as well as save concerts they are interested in.

The API:

Setting up the API was pretty straightforward, as I had done this for my last project. I used the 'rails new' command with the '--api' and '--database=postgresql' flags to set up my basic files. I decided to use postgresql again because I plan to host my app on Heroku later on. Here is a helpful blog to help set up a rails app with postgresql.

My models are as follows:

User

  • has_many :saved_concerts, dependent: :destroy
  • has_many :concerts, through: :saved_concerts
  • belongs_to :location

Concert

  • has_many :saved_concerts, dependent: :destroy
  • has_many :users, through: :saved_concerts
  • belongs_to :location
  • belongs_to :artist

Location

  • has_many :concerts
  • has_many :users

Artist

  • has_many :concerts

SavedConcert

  • belongs_to :user
  • belongs_to :concert

I had several validations as well:

  • Users require presence of unique username, and presence of name.
  • Locations require presence of city, state, and country.
  • SavedConcerts records must be unique.

If you want to make sure you are only saving unique records, you can use the :uniqueness validation with the :scope option and pass :scope the remaining attributes for the model. For example, this is the code I used to make sure I was only saving unique saved_concert records in my database. This model only has concert_id and user_id attributes, so I checked for uniqueness of concert_id in the scope of user_id.

validates :concert_id, :uniqueness => {:scope => :user_id, message: "already saved"}

My controllers:

I used the controller rails generator to set up my controllers. They are as follows:

  • Concerts Controller - Actions include one for getting concert data from an external API and another renders concert data from my database.

  • Users Controller - This has an action to create a user and an index action to show all users (although the index is not being used yet).

  • Sessions Controller - Create, destroy, and getCurrentUser actions are used to login, logout, and render the current user from the sessions hash.

  • SavedConcerts Controller - This controller has actions to create a saved concert and render an index of a user's saved concerts.

For serialization I chose to use the fastJSON api gem again. I set up serializers for Concerts, Locations, Artists, and Users. For users and concerts, I have custom attributes to display location data in a way that was more helpful for my front-end:

attribute :location_json do |concert|
    {
      city: concert.location.city,
      state: concert.location.state,
      country: concert.location.country
    }
end

React / Redux:

To start out my React app, I decided to make a new repo because I figured that the API and the front-end could be considered two separate applications. I used the 'create-react-app' command to set up the basic files.

The components:

The app has five class components and six functional components. I only used class components when my component needed local state or used one of the lifecycle methods such as 'componentDidMount'. Although, I could have put all of my state in redux, I chose to have local state for my login and signup forms in order to make them controlled in a more straightforward way.

Redux:

My redux store ended up with the following keys:

  • currentUser: This stores an object with information about the logged in user.
  • upcomingConcerts: This data is fetched from my API when the upcomingConcerts component mounts and is based on the location of the current user.
  • savedConcerts: This data is also fetched from my API when the savedConcerts component mounts and is updated when a user clicks the button to save a concert.

I created separate reducers for each of these pieces of state and used the 'combineReducers' function to combine them and pass them to 'createStore.'

I applied Thunk middleware to my store in order to have my action creators dispatch additional functions when I made asynchronous fetch requests to my database. Usually these dispatched functions returned actions to update my redux store if my fetch was successful. I have a blog about Thunk if you would like to do some more reading about its uses.

Lesson Learned: Don't store secret keys in React

I came across an interesting problem when I decided to get real concert data from an external API. After doing some research, I found out that there is not really a secure way to store secret keys in React, so I decided to store my API key using the dotenv gem in my rails app and then fetch the data from there. Here is a helpful blog if you would like to do this yourself.

React Router

One final thing to note about the client side is routing. The app currently has 5 routes. My home page has a route of '/' and will conditionally render the welcome page or the upcoming concerts and navbar components depending on if the user is logged in or not.

The welcome page uses Switch to wrap the '/login' and '/signup' routes to display one form at a time. The MainContainer (which is displayed when a user is logged in) defaults to display upcoming concerts, but switches between '/saved_concerts' and '/upcoming_concerts' using NavLinks in the NavBar component.

I found NavLinks from the 'react-router-dom' package to be useful since you can give them an 'activeClassName.' This allows you to style your links conditionally when a user is at that path.

Looking Ahead

Although I am wrapping up this project for now, I would like to add the ability for users to connect with each other through a messaging feature in the application. I will most likely add a concert review feature as well where users can upload their pictures, and rating of the show.

Thank you for reading! Here are the links to my repos if you would like to check it out: Rails API / React

Let me know if you have any suggestions for my application.

Wayne's World

Top comments (0)