Final Project: React
Well, I’ve made it to the final project. All there is left is to complete the review and I’m on to graduation from the Flatiron School software engineering program. Over the last few weeks I’ve been working on an application utilizing React and Redux on the front end and Rails as an API. The application is a re-do of my Sinatra project, creating a platform for hockey players to set up teams and track various season and game stats for their teams and players. I’ve had a whole lot of ups and downs working on this one, so lets get into it.
Next up — models. Hope you’re ready for another conspiracy theory board:
The general flow is that a user will create an account, then they can either create or join a team (or both — there’s no limit). Assuming the user has created a team, they can then add players to the team. Players begin as a standalone model, but can be linked to a user by redeeming a player code created by the team owner. From here, the team owner will build out seasons related to the team, and games related to the seasons. Prior to a game being updated to a status of final, players can flag themselves as attending or not attending the game (or the team owner can flag players, if the player isn’t updating on their own, or if a player hasn’t created an account yet). As the game is played, or post-game, the owner can add goals and penalties from the game. Once game data has begun being populated, team, season and game statistics can be viewed from the various show pages.
For the front end, lets start out with routes. Starting out, I used this reference for the basis of the routes that require authorization. In my implementation, I’ve imported the
react-router-dom for routing, and used it in two components — app.js and PrivateRoutesContainer.js. There are three public-facing routes,
/signup, as well as a “private route”, which matches anything past
/ that hasn’t already been matched.
PrivateRoute checks local storage to see if a user has been stored, and allows access to all other routes once confirmed. Clearly this isn’t a foolproof setup, as a nefarious user could easily set their own cookie; however, bypassing this will not allow access to the Rails API, as a JWT stored in an httpOnly cookie needs to be set by the Rails server after authentication.
Once authenticated, the user has access to the second router, in
PrivateRouteContainer, allowing them to reach their dashboard, teams, team show, season show and game show pages (
/games/:id ). Each route generally allows creation of related models down the line, so long as the user is the owner of that instance (
/teams can create a team,
/teams/:id can create seasons related to the team ).
Next up, local storage. I utilized
react-redux with Thunk as middleware on the front end. I think I probably overused it to be completely honest, but it was worth it for the challenge. As you all know,
react-redux allows you to hold information in a ‘store’, which can be accessed in various components beyond
<Provider /> via importing the
connect method. Thunk middleware allows Redux’s
dispatch method to accept a function, rather than an object, so that actions and services can be handled outside of the component.
In my setup, all of the main page containers past
PrivateRouteContainer are linked to the Redux store. When a user visits a particular route, the associated section of the Redux store is reset to its
initialState before being updated from the Rails API. When a user creates, updates or destroys an object, the store is updated, on most routes in the same ways as from our lessons —
.map(thing => thing.id === action.thing.id ? update : dontUpdate),
.filter(thing => thing.id !== action.thing.id). That is, up until the games page.
On the games page, things got significantly more difficult because I’m tracking game stats on the same page. If the owner of a game adds, updates or destroys a goal or penalty, it should update the relevant player statistics. When visited initially, player statistics are delivered from the API; however, when a CRUD action is made, only the related item is returned from the API. My solution to this, and to be clear I’m not sure this is how it should have been done, is to reconcile records in the reducers, prior to updating the store. This solution works; however, I’m not sure how well it will scale. I’ve been thinking of other ways around this, but for the time being it works reasonably well.
Last thing I’d like to cover, I have several places where data is input throughout the application to create or update objects. These are all controlled components, where the value of the input is read from state, and state is updated
onChange. Most of this wasn’t too bad; however, again on the games page, I ran into quite a few problems getting the goals CRUD working. A goal has a scorer, up to two players that assist the goal, and up to six players on ice (per team, including goalies). These values factor into a player’s goals, assists and plus / minus stats, and are a places that I could see being opportune for creating bad data.
To counter this, I’ve created a goalsValidator in a separate file (as well as other validators for other classes, but this one was the most in depth). Every change made to the new and update goal modals is validated prior to updating a components state, reducing the number of bad requests potentially sent to the API. Coupled with equivalent validations in the Rails models, this should entirely weed out any bad data sent to the server.