Side Project - React Sokoban
Over the last weekend I started a small project to learn more about React and to try new design patterns.
The challenge was to learn as much as possible in React and yet make something publishable in two days. This blog post will summarize the project and what I learned.
Here were the conditions to start this project
- Practice with React
- 2 days deadline
- Have something that I can publish to "production"
- Don't forget about composition and declaration
- Avoid HOCs
- Don't make another todo-app
- Write a blog post at the end
You can find the project here.
The source of the project: ArmandDu/react-sokoban
To fit those requirements I decided to make a simple game.
To be a bit creative, I chose the Sokoban game.
The app had to contain a couple of pages:
- A Home page where you can choose between the different options.
- A Play page where you can choose a level and play it.
- An About page that explains the motivations to make this app.
- A Help page to explain how the game works.
A Settings page to configure the controls, theme,...
And some functionalities:
- Be able to choose between a few levels.
- Move the boxes as in the Sokoban's rules.
Have a moves counter.
- Tell when a level is won.
- Restart when stuck.
- Have at least 2 different controls.
Because two days can be really short, I skipped the setting page and some components are a bit dummier that I had expected.
First, before starting to code I needed to figure out what boilerplate to use. I'm used to playing with Meteor but I wanted a standalone client app, no need to setup a full Meteor project. I already knew about a couple of solutions such as:
- create-react-app : the config less tool to start a react app
- React Webpack babel : a react + webpack + babel starter kit
- create my own boilerplate : setup everything myself
I thought of starting my own boilerplate but since I had really little time and I wanted to practice my React, not my webpack skills, I chose to use create-react-app. Moreover, using create-react-app let me see how they handle and package things for production too.
Still before starting to code, I had to choose what to use or not to use. These were quite simple descisions.
- Flux/Redux: As the app was simple enough I didn't even bother to use Redux or anything like that.
- Router: I used react-router 4 for my routing needs and to be able to split the logic by route.
- Type Checking: I thought about trying TypeScript for a second but, again, my goal was to practice with react, not flow. So I decided to stick with React's PropTypes.
- Styling: I didn't think about sass/.. at all so I had to choose between css and/or inline styling.
One library that I used is "react-markdown" because, in the
About pages, I was too lazy to use <div>s, <p>s, headlines and so on.. myself. Another reason was that I wanted to give it a try and see how it worked.
Disclamer: I'm really bad with css stuff. I'm used to coding algorithms, not to making stuff beautiful.
One decision that took more time to take was about styling. How to use it? Inline style every component? Use the css module provided by create-react-app?
I decided to give it a go and use the css module. My approach was to have one .css file per component so each component is bundled with a style that tells how it should render... but I wasn't rigorous enough and let myself mix "page layout styling" and "component styling".
One major thing that I learned with that approach is to be super careful with styling.
Currently I have an
index.css that do some page layout styling. Then, in my root component
Startup, I have a
Startup.css that further edits the page layout styling. Then
Home that does the same, then I have other components that update more or less their own style and the children styles.
For instance I have a
Playlevel.css file that has a
.level-complete rule. It displays a green badge when a level is complete.
This rule is quite complex because it needs to know where and how the level is displayed in order to be placed at the right position.. That's why in
LevelList.css you can find another rule
.level-complete that changes the rule to display the content more or less in the middle of the level's preview section. In my opinion that is quite messy and should be handled in a better way since it's extremly complicated to track issues and to avoid side effects.
My next assumption that I'll try to validate in the next time is that:
If using css, I should have a main css file (index.css) that will be in charge of the overall page layout (like body, headlines, sections, ...).
When styling a component I should only stick to how that component should render but let its parent handle it's size, position, and so on. In the case of the
.level complete, maybe create a dedicated component to display data for the preview's section and another one to display data when playing.
There are some design patterns that I experienced with.
I tried to minimized the use of HoC in favor of the RenderProp design.
One benefit is that I know where my data is at all time. One downfall that I haven't tried to fix yet is that when the component that uses a renderProps renders, it forces all children components to re-render since it will call the renderProp function again.
Because a game needs logic.
In this section I'll write more about the game and the choices I made to make it.
I wanted something simple to load the data to my component. The levels are stored in static .txt files.
I wrote a helper to parse the files but I needed to find a way to load that data in an asynchronous way.
PlayerContainer is already in charge of so much (handling the game logic, rendering a level or the level list), I wrote a sort of dummy component that takes the list of categories, fetches the levels and then return the list sorted by categories.
I tried a couple of controls in this app. I'm currently using a
SwipeControls (for smartphone & tablet).
I was planning to have a settings component to configure/disable the controls but as my two days deadline was approaching at the end I just wrote what was needed to control 1 player. (The PlayContainer can handle N players and I have an unused component "PadControls" that renders a pad for each player.)
About the SwipeControls. As it was messing with the page scroll, I used a ScrollLock component to stick the page to the bottom so it's playable with a mobile phone. If I ever update this project I'll spend some time on the controls and removing this "hack".
To conclude with this project, this was a nice two-day project!
I had the opportunity to learn more about styling and some design patterns to replace those HOCs that were bugging me.
This post is one first draft, I might edit it in the future or make another one with more code explanations.