What I learned while building a Snake Game with React and Recoil
Recoil is a new state management library developed by Facebook used for React applications. It has a very interesting concept that promises simplicity, but with powerful capabilities. Unlike redux, it has a simple framework without messy boiler plates. I decided to take a look and see how it works. However, instead of making a simple app, I thought of building a game with it. I ended up building a Snake Game to fully test my understanding of how the library works. I know some will say I don’t need to have a sophisticated state manager to build this game and I certainly agree. However, I also believe that the most effective way to learn a technology is to apply it in an unusual way or on a more complicated application. Writing a game in React is unusual and complicated, but is possible. It’s a perfect way to learn Recoil.
Unlike redux and react context, Recoil has the concept of multiple units of states called Atom, where components can subscribe to. Components will render only when the atom where they subscribe for changes. This will avoid unnecessary rendering when state changes. An atom can be defined by using the atom() function. An atom should have a unique key and a default value for its state. On my game I have created 3 separate atoms that represent its own data:
The SnakeTailState holds the information of all the snake’s tail location, by default it has 3 tails. The FoodState holds the location where the food will appear in the screen. And lastly, the KeyPressState will hold the keyboard entries that will tell the direction of the snake.
Recoil is designed for React Developers who love hooks. Yes, if you love developing functional components and use hooks a lot then you will enjoy the benefits of recoil. Recoil has some ready made hooks in order to access and update atoms.
- useRecoilState(stateKey) returns a tuple where the first element is the value of state and the second element is a setter function that will update the value of the given state when called.
- useSetRecoilState(stateKey) returns a setter function for updating the value of writeable Recoil state.
These sample hooks are just among the other hooks that you can use to access and modify your atoms. On my code I used useRecoilState to access the SnakeTailState and pass it to my snake component that display it on the screen. While useSetRecoilState is used to update the KeyPressState everytime a user pressed the keyboard.
Selectors are functions or derived state in Recoil. Recoil can have a get and a set function. Get functions can return calculated values from an atom or other selectors. A get function does not change the values of the state. However, a set function, also called a writeable selector can be used to change or update a state.
As you can see on my selector, I built the following logic that corresponds to my states. These selector can communicate with other atoms and other selectors to build a new set of states.
- Calculate how to create new tails whenever the snake has eaten the food.
- Decide where the food will randomly appear on the screen.
- Check the snake’s next direction based on the key pressed.
- Check if the food was eaten
- Check if the snake hit the walls
I don’t have to write those logic inside the presentation layer which made my code very clean. All I have to do is use Recoils helper hooks to access the selectors from the presentation layer, the same way I access an atom.
Findings and opinion
For me Recoil is a better choice on state management. One reason is that it promotes one of the SOLID principle, Single responsibility principle. By designing your state to have different units of state that represents one thing you avoid making a convoluted state.
Why does a single global state is bad?
If our app is simple, probably we can get away with it. But, as our app becomes larger and more complicated, having a single global state that holds everything will be problematic.
Imagine our state as a database
When we first design our database, do we build one table to represent our data? Unless we have a very good reason, a database should always be normalized. Each tables on our database should represent one particular data, example: Employee, Department, Salary etc. And I believe that states should also be designed the same way. Each state should only represent a particular set of data.
On a database, if we want to combine rows between tables, we can define a view. On Recoil, we can do the same by using selectors.
Building a game with React is fun though not recommended, but it helped me understand Recoil much better. I will probably continue this by writing another version of my snake game app using Redux and share my experience.