DEV Community

merlumina
merlumina

Posted on

Boardgame Scheduler: React/Redux Project

Today I'm going to talk about Boardgame Scheduler, my final project for Phase 5 of Flatiron School's Software Engineering Program. Boardgame Scheduler (admittedly, my least creative app name yet) is an application for managing boardgame collections and scheduling time slot for playing games. I got the idea to create an app like this because I want to hold a miniature "convention" as a wedding reception and thought having a feature like this for my website where guests can let us know ahead of times what game they'll be bringing would be a useful too. The app is built using React/Redux for frontend and Rails API for the backend. The frontend is styled using Tailwind CSS.

Backend

Models

Cardable has four models, Users, Games, Tables, and TimeSlots. Users log in with a username and password and can have many games. Games have a title, number of players, suggested play time, description, and can have many TimeSlots. Tables have a "location" and also have many TimeSlots. TimeSlots represent "events" and have a start time, end time, as well as an "All Day" boolean which are all necessary properties for configuring react-big-calendar, a package I used for the scheduling feature.

Sessions

I decided to implement logging in/out with session cookies rather than JWT tokens. Cookies are not available to API-only Rails configurations out of the box, but can be used by adding the following to your application.rb:

    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore
Enter fullscreen mode Exit fullscreen mode

The React + Rails API Authentication series by edutechional on YouTube was immensely helpful in getting authentication up and running for this app! I highly recommend it for anyone struggling to get authentication for a React/Rails app.

Frontend

Example Redux flow for creating a game

We'll start with the game form.

// src/components/games/NewGameForm.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { addNewGame } from "../../redux/actions/gamesActions";

class NewGameForm extends Component {
  state = {
    name: "",
    numberOfPlayers: "",
    time: "",
    description: "",
    user_id: this.props.user.id,
  };

  handleSubmit = (event) => {
    event.preventDefault();
    this.props.addNewGame(this.state);
    this.setState({
      ...this.state,
      name: "",
      numberOfPlayers: "",
      time: "",
      description: "",
    });
  };

  handleChange = (event) => {
    const { name, value } = event.target;
    this.setState({
      [name]: value,
    });
  };

  render() {
    return (
      // insert form here
    );
  }
}

const mapStateToProps = (state) => {
  return {
    games: state.games,
  };
};

export default connect(mapStateToProps, { addNewGame })(NewGameForm);
Enter fullscreen mode Exit fullscreen mode

While this app does rely on Redux for state, since the information about a new game is only needed within this component long enough to send it to the API and won't be used by other parts of the app, the values derived from the form are stored in the components state rather than being sent to the Redux store. The user_id is set from the current user saved in the store passed in as a prop to the form.

When the form is submitted, the addNewGame action is called:

// src/redux/actions/gameActions.js
export const addNewGame = (game) => {
  return (dispatch) => {
    fetch("http://localhost:3001/api/v1/games", {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({
        game: {
          name: game.name,
          number_of_players: game.numberOfPlayers,
          time: game.time,
          description: game.description,
          user_id: game.user_id,
        },
      }),
    })
      .then((response) => {
        return response.json();
      })
      .then((game) => {
        dispatch({ type: "ADD_NEW_GAME", game: game.game });
      });
  };
};
Enter fullscreen mode Exit fullscreen mode

Which in turn dispatches the "ADD_NEW_GAME" action to the reducer:

// src/redux/reducers/gameReducer.js
export default function gamesReducer(state = [], action) {
  switch (action.type) {
    // ...other actions...
    case "ADD_NEW_GAME":
      return state.concat(action.game);
    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

With the freshly created game returned from the API and added to the games array in the store, the games page is refreshed to show that the new game has been added.

Table Time Slots with React-Big-Calendar

The last thing I would like to point out is the use of the react-big-calendar package, which I found to be quite interesting to use.

On my Tables page, there is a TableCard component generated for each Table object, and there is a Calendar component for each TableCard. The Rails serializer for my Table model includes all of the TimeSlots (scheduled play sessions for a particular game) associated with that table, so it is easy to get all of the TimeSlots from state. This is important because the Calendar component requires an "events" prop to be passed, so the list of TimeSlots for each table becomes the list of events displayed on the calendar.

Next, the Calendar includes an onSelectSlot prop which is a function that fires when a time slot on the calendar is clicked. Luckily, this event includes a lot of useful information, including a Date object corresponding to the time that was clicked on in the calendar. Using this, I created a setStartTime function that will set the start attribute for the TimeSlot to the start time from the event. It also pops up a form where you can choose the game and duration of the event. Here, the TimeSlot is already populated with a start time, and the end time is created by adding the duration (in hours) from the form.

// TableCard.js
          <Calendar
            date={new Date(2021, 9, 8)}
            onNavigate="defaultDate"
            localizer={localizer}
            events={this.props.table.time_slots}
            onSelectEvent={(slot) => false}
            onSelectSlot={this.setStartTime}
            selectable={true}
            startAccessor="start"
            endAccessor="end"
            timeslots={1}
            step={60}
            view="day"
            onView="defaultView"
            toolbar={false}
            header={false}
            min={new Date(0, 0, 0, 10, 0, 0)}
            max={new Date(0, 0, 0, 18, 0, 0)}
            style={{ minHeight: 200 }}
          />

  setStartTime = (event) => {
    this.setState({
      start: event.start,
    });
    this.toggleModal();
  };

// NewTimeSlotForm.js

  handleDurationChange = (event) => {
    this.setState({
      ...this.state,
      end: String(
        add(new Date(this.state.start), {
          hours: parseInt(event.target.value),
        })
      ),
    });
  };
Enter fullscreen mode Exit fullscreen mode

Those are the highlights from my Boardgame Scheduler app! Please feel free to check out the repo here:
https://github.com/lizriderwilson/boardgame-scheduler

-Liz

Discussion (0)