Benji Riethmeier
Icon

Courtside Devlog 10: The NHL Has Arrived!

4/10/2023 | 7 min read

What is Courtside (Currently)

The backend for NHL statistics has been fully fleshed out. This means that finally, the user can now switch between seeing data for the NBA and for the NHL.

The user can see schedules for upcoming NHL and NBA teams, as well as follows stats from either sport. Currently switching between sports is implemented as a toggle switch in the app header, but in the future I will be exploring other options.

In other news, the functionality for following different players is making good progress due to hard work from RJ!

This Week

The tasks I completed this week all involved the NHL integration. The main components of this integration were:

  1. Editing our backend data routes to allow users to specify which sports league they want data from
  2. Creating a new React Context to store data about which league a user is currently seeing data for
  3. Allow users to toggle the league on the frontend
  4. Editing the Tanstack Query functions on the frontend to match the backend

Using the Stat Leaderboard screen as an example, let’s go through these steps!

The Backend

Fixing up the backend was not too difficult. Riley and Jake had ensured that the data for the NHL was in the same format as the data for the NBA. So the big task here was just adding the option to select a league.

Originally, the route looked something like this * Of course, in the project the aggregation pipeline and other logic is much more complex, but it is simplified here for easy understanding :

@app.route('/leaderboard/<stat>', methods=['GET'])
def get_leaderboard(stat):
	leaderboard = db.leaderboards.aggregate([
		{ '$match': { '_id': stat }}
	])

	return json.dumps(leaderboard)

This route can then be adjusted slightly to allow the client to specify which league it requires data for:

@app.route('<league>/leaderboard/<stat>', methods=['GET'])
def get_leaderboard(league, stat):
	if league == 'nba':
		collection = db.nba_leaderboards
	elif league == 'nhl':
		collection = db.nhl_leaderboards
	else:
		response = make_response()
		response.data = "Currently the NBA and NHL are the only supported leagues. Please use 'nba' or  'nhl' for the parameter."
		response.status = 400
		return response

	leaderboard = collection.aggregate([
		{ '$match': { '_id': stat }}
	])

	return json.dumps(leaderboard)

Basically, we choose which MongoDB collection we are querying based on the league parameter and then if the parameter is not a valid league, return an error message and a 400 Bad Request status.

Repeat this process for the other data routes, i.e., /schedule, /team, /player, and /score and the backend is ready to go!

Contextualizing

Trading in the Python hat for the JavaScript hat, I then moved to the front end where a new Context * Contexts are a way of sharing data between components without having to send that data through every layer of the component tree. had to be created to store the league the user was currently viewing.

This Context only had to have the league data and a function to change that league data, so this was also pretty simple.

 //make sure the client can only use a supported league
type League = 'nba' | 'nba';

// set the valid types allowed as data in this context
type LeagueContextData = {
	league: League;
	setLeague: (league: League) => void;
}

// create the context!
export const LeagueContext = createContext<LeagueContextData>(
	{} as LeagueContextData
)

Then I created a Provider component that would provide this component to the app and placed the Provider in the entry point to the app: the App.tsx file.

export const LeagueProvider = ({ children }) => {
	const [league, setLeague] = useState<League>('nba');
	const data : LeagueContextData = {
		league,
		setLeague
	};

	return (
		<LeagueContext.Provider>
			{children}
		</LeagueContext.Provider>
	);
};

With this, the league data can be accessed anywhere in the app with the useLeague hook, which is just a wrapper around the useContext hook.

Switching Sides

Then I created a simple component to toggle between the leagues and added it to the header of our main Tab Navigator.

const SwitchLeagues = () => {
	const { league, setLeague } = useLeague();

	const handlePress = () => {
		if (league === 'nba') setLeague('nhl');
		else setLeague('nba');
	}

	return (
		<Pressable onPress={handlePress}>
			<Text>{league.toUpperCase()}</Text>
		</Pressable>
	);
}

Finally, to make this toggle have an effect on the data in the app. The Tanstack Query functions which handle the app’s data fetching have to be adjusted. Returning the stat leaderboard data, the function takes in a list of stat IDs and returns the data for all of them. Here is what the function looked like previously:

const useStats = (stats: string[]) => {
	return useQuery({
		queryKey: ['stats'],
		queryFn: async () => {
			const statData = [];
			for (let stat of stats) {
				const { data } = await axios.get(`/leaderboard/${stat}`);
				statData.push(data);
			}

			return statData;
		}
	})
}

This function can be updated to depend on league data with our new useLeague hook!

const useStats = (stats: string[]) => {
	const { league } = useLeague();

	return useQuery({
		queryKey: ['stats', league],
		queryFn: async () => {
			const statData = [];
			for (let stat of stats) {
				const { data } = await axios.get(`${league}/leaderboard/${stat}`);
				statData.push(data);
			}

			return statData;
		}
	})
}

And once all the data fetching functions are updated, our app has gone completely multi-sport!

Currently, only the stat leaderboard and schedule data are fully implemented on the frontend. However, once a few more backend tweaks are made, team data should also be ready to encompass the NHL!

Problems This Week

After all the build up to this feature, I had anticipated its implementation being much worse than it actually was. Ultimately, the bulk of the work came from updating all the API endpoints and data fetching functions, which ended up being more monotonous than anything.

The only issues were just data holdups from the backend team. The NHL data for teams could not be implemented because the team data in the backend is missing a few properties: notably the link for the team’s icon and the team’s color.

Team Color data is fundamental to customizing the user's theme

Team Color data is fundamental to customizing the user's theme

These properties are fundamental to a few features of the app (e.g., the team icons being displayed on everything, user theming build on the primary colors of their teams), so I decided to finish the multi sport integration next week once that data has been added.

Next Week

The big project next week is going to be completing the implementation of the multi-sport data for teams and players. Once all the properties in the database, this should be pretty easy.

Beyond this, there are many directions that this integration could be expanded. For one, I see many opportunities where the app could be improved by not having the sports strictly separated.

For instance, we could have a Schedule page that shows all the user’s sports, allowing the user to filter the schedule by what sport they want to see.

Mock up of Game Display on an all sports schedule screen

Mock up of Game Display on an all sports schedule screen

Another example is the settings page, where the user should be able to choose a primary theme based on any team they are following, not just the teams from the currently toggled league.

This integration also needs further work to allow users to not follow a given league so that the sports in the app are only sports that they want to see. Out of all the features listed here, this last one is the most important to the spirit of Courtside and will be high priority next week.

Pet Projects

Besides this big integration, I am still playing around with the new Expo Router as described in my last post , which will hopefully simplify the navigation system for the app and provide the frontend team with a better developer experience. My work on the router was put on hold this week due to the integration work, but I was able to update the main app to Expo SDK 48, which is the minimum version required to use the Expo Router.

However, that is all for this week. I will be back next week with another update! Bye!