A Tale of Two Codebases

Rate this content

Anybody can write code that “works.” But what makes the best engineers stand out is their ability to create solutions that are clear, concise, testable and easy to understand and maintain. Join us as we explore two of React’s most powerful tools for well-architected solutions by starting with a suboptimal game codebase and refactoring it using industry best practices such as custom hooks, higher-order components (HOCs) and contexts.

- Introduction of the initial implementation and description of its issues.

- Presentation of the tools available to improve the code base.

- The mindset behind architecting clean solutions and refactoring.

- Highlevel passthrough of the code and creation of the plan.

- Extraction of code into utility functions.

- Extraction of state and effects into custom hooks.

- Grouping behavior into HOC.

- Creation of root level context and child components cleanup.

- Result overview and discussion of possible future improvements.

- How to keep your code clean, rules to follow and limitations to impose.


A computer with git installed and a GitHub account.

139 min
13 Oct, 2022

AI Generated Video Summary

The Workshop focused on fixing and improving existing code, using tools like custom hooks and context. Refactoring was emphasized, with an emphasis on mindset and small steps. Utility functions, custom hooks, refs, constants, custom types, and context were highlighted as powerful tools for code quality. The implementation of an events system using React's standard library was discussed. The Workshop also touched on improving code reusability, simplifying collision logic, and enforcing clean code practices.

1. Introduction to Workshop

Short description:

Welcome to the workshop! Today, we'll focus on fixing and improving existing code. Clone the GitHub repo to get started. We'll explore the main branch and go through the refactoring steps. React may not be ideal for games, but it adds a fun twist. Let's dive in!

React-Based Courses Hi, everyone. Welcome to the workshop. I'm really happy that you've guys come. My name is Michael Tintouk. I'm a Principal Consultant at Modus Create. It's a digital agency, and the cool thing about that is that it allows me to work for various clients, big, small, and huge.

And over the years, I've worked as a tech lead, and that allowed me to architect and build applications and solutions from scratch, update older code, and of course, do a whole bunch of refactoring. And this is the main topic of today's workshop. It's how to fix existing code, good or bad, improve good code, and fix the bad ones, and how to keep it clean throughout the ages. So, we're going to go through how to do it, how to achieve that. So, I'm going to ask you to clone the GitHub repo that I've shared, and if you don't have access to it, to Discord, just let me know. We'll sort that out.

So, clone the repo, we're going to be looking at the main branch to start that has the bad code, so to speak, the original code that we're going to be refactoring. And, the codebase is not huge, but we're not going to do refactoring live because that's going to take us a week, pretty much that's what took me. But, I'm going to go through the pull requests. If you have a look at them, there are eight of them. So, there are refactoring steps, which I will go through and basically explain my thinking, the steps that I took, the decisions, etc., etc.

So, why a game? React is probably not the best choice of a framework or a library for a game. So, this is really not focused on game development in any way. We will briefly touch on a few concepts and keys. But this is just to make it a little bit more fun. We've all done enough to-do apps in our life. So, it's time to make a change. So, we'll look at the code a little bit later. I just want to brush up on a few keynotes here.

2. Challenges of Bad Code and Available Tools

Short description:

Bad code can cause missed deadlines and developer fatigue. The code base may be large but lacks extensibility and maintainability. The player component is particularly problematic, with unclear functionality and poor code structure. Understanding and making changes to this code would require navigating through hundreds of lines. However, there are tools available, such as custom hooks and context, that can help improve the code.

So, why bad code is bad? That's a pretty good question. It's going to cause missed deadlines, it's going to cause developer fatigue. I'm sure you've all worked on a client or on a project where a scrum master asks whom should they assign a ticket and the whole room is just dead. Nobody wants to touch it because they already dread the idea of going into that part of the code and touching it. It's like the no go zone of the code. And that's the code smell. So we will try to not do that.

If we're going to look at the initial code and the final version, functionally they're going to be the same. In fact the code base at the end will be almost twice as big. But it's going to be better in terms of the fact that we're going to create systems that are going to be reused across the board.

Hold on just a second. You will see cats going in and out of the camera and they might push a button. So if I'm muted, please let me know. So going back, the main code base is impossible to extend the NAT features to. In fact, I hit that roadblock when I was writing the initial code from the main branch. I wanted to add more features and make it nicer and more cooler, I guess, I don't know. But the code that I was working with already was impossible to extend and maintain. So then I understood that, okay, enough is enough. It's time to refactor, it was impossible to add new features at all.

So let's have a quick look at it. Just a second, Zoom is in the way. All right. So you should all be seeing the code, right? Yeah. Okay. Thank you. So the entry point is as usual, nothing to see here. Nothing to see here. It doesn't look too bad, right? And by the way, is the font size okay? Everyone can see the code fine? Maybe a little bit bigger. It can. Yes. Can do. It should be better. So straight out of the gate, you can see that some of the code here, some of the state here is probably shouldn't be here. Like is the cellar door open or closed? Is the liver used? Player health? Like the stuff here. Uh, that's fine. I mean, we can work with that. We're passing some of the stuff to our player components. Um, and I will not go in too much depth of how it works or how I've compositioned, um, ideas to kind of teach the concepts on a broader level.

So let's go and see our crown jewel, the worst part of the code I've seen in a while, and I actually wrote that. So let me quickly scroll and you can marvel the horrible stuff that it is the player component. So what can we see? Nothing. It's a blur. It's a mess. What does it do? I have no idea. I see a bunch of get element by id in React, which is bad. Like never do that. Why would you do that? I'm seeing some numbers which have no meaning whatsoever. I'm seeing nested ifconditionals to no avail. They never end. So what does this do? I see some draw image calls. Parse and I have no idea what's going on. Nested conditionals. So to understand if you were given this piece of code and you were asked fix such and such or add a feature, remove something, you would have to literally go through almost 300 lines of code and understand how this piece works. And, besides that, probably investigate how the other parts of the code interact with it. Because the props are governing it like the health, the un-interact, on-collision, what do they do? No comments, nothing, no semantic code. So this is about it. This is the worst part. It kind of gets a little bit better in other components. Like, let's see the animated fire. It's quite simple, just a sprite that animates. No, it's not too bad. But, again, without writing the code, it's very unlikely that you would understand or grasp the concept of what's going on. Like what these numbers are. Why is it an interval? Etc. Let me go back to my slide deck.

So we have a bunch of tools that we can use to help us negate all of that. So in our code we saw that we've used a bunch of built in hooks like useState and refs and whatnot. But they were used poorly. So we're going to use custom hooks, references, context, and more such as utility functions, etc.

3. Refactoring and Mindset

Short description:

We're going to use custom hooks, references, context, and more to clean up the code and build reusable systems. Having the right mindset is crucial. Understand the concepts rather than relying on copy-pasting. Think ahead, avoid overengineering, and consider testing. These concepts apply across languages and frameworks. Refactoring is a balancing act, extracting repeatable patterns and adapting components. Keep it simple with smaller functions and envision a better code base. Refactoring takes small steps, like a puzzle, and you'll get there.

So we're going to use custom hooks, references, context, and more such as utility functions, etc. to help us clean this mess up and to, most importantly, build systems that we can even repackage and maybe share with the community or within the company so that other teams can use and build upon them.

But most importantly, we have our mind, right? And what I found throughout the years working in the industry is that you need to have a specific mindset to write code like that. And I'm not saying it as something unachievable or something that you have to be born with. I don't think of it as a talent. I think of it as a skill that everyone can acquire given the specific guidance and maybe a few hints and something like that.

So what is the mindset? Copying and paste is fine, just like this meme here. You're not sure if you write good code or not. If you're just copy-pasting stuff, you're relying on someone just providing you a solution. So the first thing that we should think about is not searching for solutions but trying to grasp the concept that that solution is trying to work on or to achieve. And try to get out of the copy-paste routine as soon as possible. It's very important and essential during the learning process. We all do that. We can have 20 years of experience and pick up a new framework. And we're going to go through docs and probably copy-paste some examples. So this is very important, but we have to go past that as soon as possible. So not use those as crutches. And adopt solutions, but understand the key concepts. It's very important to think ahead and look into the future and see it. You have to have a vision for a good code base. Even if you're starting out as a sketch, as a doodle, you have to understand that you might be drawing a Mona Lisa. But you have to have it in your head and understand that, OK, I'm doing something messy right now, but I'm going to improve it later on. I'm just doing a proof of concept, for instance.

And don't overengineer. Sometimes that will lead to code bases that are even worse than just straight ahead basic code. And it is very important to think how you're going to test any given functionality. Is it a piece of function, a function, a class, whatnot? Just think how you're going to test it. This helps. Don't stress out about it too much. But keep that somewhere in the back of your mind. And you will probably notice that a lot of what I'm talking about is not React specific. And this is on purpose, basically. I think that these concepts apply throughout the languages that we use and frameworks and libraries. I think that they are essential to writing good code and being a great developer.

Refactory is coming. Yes, praise for Impact. Praise for the long haul. Get ready, because it's not a one day job most of the time, especially if it's a large code base. Refactoring, I'm sure most of us have done it to some extent. It's a balance act between over engineering and doing too basic stuff. The idea is to see repeatable patterns, and yank them out, and put them into something that you can import and reuse across the board. But the idea of a balancing act comes in when you understand that you may be doing the wrong abstraction. This is a very important concept when you're going to take out a piece of code that is too broad, and you're going to try to adapt all of the components throughout the application or part of the application to adhere to that. So you're going to see functions that have too many branching paths call other functions, whatnot. So try to keep it simple. Always prefer smaller functions, and a myriad of them, a lot of small functions. Single responsibility should be one of the key ideas that you're trying to accomplish while doing your refactoring. And again, try to see the future. Envision a better code base. So if we go back to some of the, like, let's pick up this smaller one, the fire. So we took some time trying to understand the code. And you'll see a lot of stuff like this here. This is not perfect for a game by any means, so please excuse my use of direct access of DOM. But basically, we're setting the position of a sprite of a fire here. So we understand that. Moving forward, we see that we are applying an image to it where we're going to draw the fire from. That makes sense. And here, we're drawing the animation on an interval. Now we understand what is going on, but how would you envision it to be better? So I'm seeing all of this could have been a single function call. All of these could have been constants. So that kind of approach you have to think of the end result basically. It doesn't mean that you will get there in one pull request in one go. And most likely, you shouldn't be doing that. Think of a few steps you're going to take. So I'm going to refactor this. I'm going to refactor some of this, for instance, and so on and so forth, and start small, and small strokes. And you will get there. I like to think of refactoring as taking something apart, for instance, this controller. You can take it apart and reassemble it, put it back together, and yet you have a few spare parts. But it still works.

4. Refactoring and Planning

Short description:

Refactoring is about making code better and staying grounded. Don't try to change everything at once. Focus on one problem at a time. Be prepared for new bugs and plan to improvise. Great developers can think on the spot and find alternatives. Create proof of concepts and avoid untested solutions.

This is what refactoring is to me. Making it better and having some leftover stuff that you don't need. It is very important that you keep yourself grounded. Don't try to change the world overnight. So try to limit yourself to one problem at a time. And we touched upon that a little bit. Sorry. All right, my bad. You should also be prepared that you're going to introduce new bugs or uncover hidden ones. So that's totally fine. This is part of the process. You should probably create some sort of plan, if not on paper somewhere in your head. But know that your plan will most likely fail. And you'll have to improvise and think quickly on the spot. And this is what makes great developers stand out from good developers. They are very good with thinking on the spot and finding alternatives when something that they've hoped for doesn't work out. And it's very important that you create proof of concepts and you don't buy into untested solutions. Say you've been researching something on stack overflow and there's a whole bunch of code that somebody suggested and you just put it in and try to adopt the whole application to that, and when you haven't even tested, is it going to work? Does it apply? Does it require some substantial changes? Try to create a proof of concept that mimics your problem or situation and work from there.

5. Using Utility Functions

Short description:

We'll be using utility functions to improve code quality. Understanding single responsibility is key. Identify components with similar behaviors and use the same utility function. Create small, reusable functions and prioritize pure functions. Consider both global and local utility functions.

So now we're going to get into more specific stuff. So these are the tools that we're going to be using and the concepts that we're going to be applying to achieve the holy grail of good quality code.

So utility functions are probably, are always going to be on the top... on the very top of my list. They're very cheap to implement, quite easy to implement. The biggest problem with them is understanding what to put in them. And, if we go back to our fire example, we could have thought like, okay, so maybe we're going to put the whole damn thing in there, or maybe the whole component into it. But really we should think about concepts and ideas and single responsibility, most importantly single responsibility. So we know that we want to draw frames. So this is what we're going to do. This is going to be a utility function. That's a pretty good example. Utility functions are probably one of the first things you would do. You would go through your whole code base, all of your components. So we can actually do it right now. So our fire and the game also has a heart and a coin. So they seem similar, right? They're static, they animate. So they probably have the same behavior. So this is what you would do. And yeah, you can see that it's pretty much the same. The only difference is the number of frames. So you would already paint a picture of which components are gonna have the same behaviors and use the same utility function. So when you're gonna write them, you understand the shape that you have to create with them. And again, create very small functions, but a lot of them so that you can change them. You can reuse them, you nest them, et cetera. And always try to prefer pure functions. And if you're not familiar with the concept, those are functions that don't cause side effects. They always return the same value if you give the same input. So try to do those and you will see in the code-base that I'm also not always following those ideas due to the necessity of working with the DOM. But those should be nice to have and trying to achieve those as much as possible. You can also have global or local utility functions. So for instance, in the other branches, I'm gonna show that we have utility functions or helper functions for the player, specific to the player, and then we have a whole bunch of global ones that are used throughout the components.

6. Custom Hooks, Refs, Constants, Types, and Context

Short description:

Custom hooks, utility functions, refs, constants, custom types, and context are powerful tools for improving code quality and maintainability. Custom hooks allow for behavior grouping and reuse, while refs provide a way to store and move data without causing re-renders. Constants and custom types add clarity and context to code, making it easier to understand. Context, although often underused, is a versatile tool for storing various types of data. It's important to use these tools responsibly and keep them small and focused on their specific responsibilities.

Custom hooks, one of my favorites, they're really awesome. You can group behavior, state effects, reuse them across components, you can package them and share them with the wider audience. They're very powerful when combined with those utility functions. So you can break apart them to be very, very specific, and that's very great. Custom hooks are basically a variant of those utility functions that are specific to React. And utility functions are more of a generic JavaScript or you insert any language, they're used everywhere.

And hooks and utility functions are not the only weapons that we can choose to slay the horrible code. refs, use them, I rarely see developers use them. Most of the time they're just used to grab a reference to a DOM element, but they're way more than that. They're basically global variables 2.0 without all of the negative context. So refs, T L E R is they are Reactive mutual function mutable data that does not cause re-renders. They are really cool, if you wanna store some data, move that around the application between components, or you don't want that to cause re-renders, but you still want to have re-renders at some point. So you can mix states and refs to control how often you're gonna re-render. We're gonna go into that a little bit later in one of the PRs.

Constants, they are a must-have. If we look at the existing code, those numbers, those abstract and unknowns, name them. So if you know the draw image call, you would know that the last two parameters basically here is the width and the height of the image that you draw, but you literally, without looking the function up, you have no idea. So if these were called width and height, it would make a lot of sense and it would add a lot of context to whoever is looking at the code. And remember, you're writing this code, not just for yourself, especially if you're working in the team, that helps other people as well. And it helps you like a week from now. And custom types. If you're using TypeScript, they are a must-have. In fact, I go as far as saying you can do type aliases for built in types like strings and numbers. So for instance, if we go back to the code again, let's pick something, number here, and number here. So if we would have a type, let's call it position, x, position, y, or just position, it would also make a lot of more sense, especially combined with the variable name. It all adds context and it takes away those milliseconds that you waste on trying to understand what the hell is going on here. Please use them. And one of the coolest ones is context. Again, from my experience, a very underused tool, I don't know why, maybe it's a little bit complex, but I don't know, it's really great because it allows you to do basically anything you want. It's one of the most powerful features. It can be used to store variables, static variables, states, reactive data, refs, functions, arrays, whatever. You can store anything in it. So with great power comes great responsibility, as we all know, so don't try to dump everything in one global context and hope that it's gonna work out very well. Probably won't. For the single, just right off the bat, whenever you update the context, it will update all of the other components. So try to keep them small. And this is an idea that we're gonna come back to all of the time. Keep stuff very, very, very small to the point with having one responsibility. You can also reuse context types, so that is even more rare in the wild. You can create a context, and use it more than one time, providing different data. So that's, again, coming to the idea of reusability. And wrap only the stuff that you think that needs the context, don't wrap the whole thing or everything in a given part of the code, only provide the context to whatever needs it. And you can then move components into the context if you think that it needs it or you're anticipating a refactor or have made changes.


Fetching Data and Refactoring

Short description:

In this part, we discussed the approach for fetching data upon user facts and the importance of not fetching unnecessary data. We also explored the first step of refactoring, which involved adding a global context and creating appropriate constants. The code was made more readable and understandable by checking the game state and using enums for tile sets. We also addressed the issue of nested IF conditionals and the importance of avoiding them. Overall, the focus was on taking small steps and making incremental improvements.

Oh, we're not there yet. Okay, so if you want to ask any questions on what I've dumped a whole bunch of information on you, maybe I can go and answer them, and then we can jump into the proper code. I'll give you a couple of seconds.

Hey Michael. What is your approach for fetching data upon user facts? Do you think that's a good idea? People are trying to avoid these days. Well, I don't really know of a different way. It depends on how you want to fetch data. Basically, it depends on the feature, but most of the time what I would do is fetch data like in a part of the application that only needs that and then store it somewhere and maybe cache it for an offline first application, but basically on-mount. But it depends. There are optimizations that you can do. For instance, you can draw a skeleton, right, and make the app usable without fetching the data, but it also depends on how crucial the data is for the component or for the application. I don't know if that answered your question.

Yeah, it does. Usually starters, they tend to focus a lot of API calls within useEffects, and that can be quite troublesome sometimes for a lot of fetch and a lot of useEffects. I've seen like multiple useEffects fetching multiple data within one component. That's kind of, yeah, I get it. So keep that thought, let's get back to it later on. Unfortunately, we're not going to have any data fetching here, but to give you a quick answer maybe to other people who are interested. Usually you fetch the data that is absolutely needed, render the application, and then use Promises, right? But you can defer all of the other calls that are not as needed for other components. But again, don't, as you said, don't fetch everything, all of the API requests and, like, at the root of the application. So I don't know if a good example would be our player script, our player, where is the player here? So if it needed to fetch some data when we would go on the road, for instance, right? You wouldn't fetch it when he's just started to render. And to the point of your note on multiple requests, I would probably put him in one use effect, but not await every request and just kind of do promise start all and wait for all of them to finish if I needed to, but not block the next request. But that's a very good question, I'll try to stick on point so that I don't lose the thread of the idea. And feel free at the end to ask the question again, if you wanna discuss it more. We're not limited by time here. Anyone else?

All right, so I'm gonna continue talking and guys, please, you can interfere and ask a question if something doesn't make sense, absolutely. Like try to stay on point so that it helps all of the members, right? So let's go through the first step of refactoring. I'm gonna show you the code in a second. So the two key things that I've done here is added a global context and created the appropriate constants. So it makes more sense and it's clear why we're working with. I'm going to read through this really quickly, but you will have access to this so you can go through the PRs at your own pace.

Alright. So this is the app, the entry point of our application. So it looks like a big chunk of code has been removed and added. But in essence, really what I've just done is added a single game state so that the game is either running or it's game over when you lose all of your health. This is everything that it does at this given time. And I've created the context and I've wrapped the whole application in it even though I said not to do that. But this is one of the first steps, again, coming to the idea of taking very small steps without making giant leaps and refactoring the whole goddamn thing. We're taking one step at a time and adding one improvement or a few small improvements, one PR at a time. That's why we have eight here. So we've added the game state and we're checking if the game state is game over and we're under the Game Over screen. And you can clearly see now that it's a little better than the other code. It's readable, it's quite understandable. You don't waste a lot of brain cycles to understand what's going on here. The other parts are quite similar. We've added the constants. Like I was saying, the width, the height, the width and height, that kinda stuff. I've created a constant to hold all of our tile sets, which is an enum. So now instead of having a hard-coded string pointing to the PNG, we have an enum that is quite easy to change in the future and it makes sense when you read it. Width and height, it all makes sense here. And this is basically identical throughout all of the files here. I won't waste our time doing that. So there's one little difference here in the Player component is that I'm also checking the GameState so that I could trigger a game over. Now look at that beautiful code. I have to waste half a day to scroll through it. So when our Health reaches 0, we're going to trigger the game over state here. And you can clearly see why nesting so many IF conditionals is really, really bad. When I'm on this line, at this point, I have to scroll all the way up to try to understand which condition has failed basically and in which condition's ELSE statement I'm in. So I'm trying to avoid that in all of the code that I write. And this is pretty much it. Let's actually look at the constants. So we have two enums, Game State, Game and Game Over. They're basically numeric ones that's found by us. And Tilesets is a string enum that we assign all of our possible tile sets. And if you're not familiar, a Tileset is an image that has all of the variations of an image in one big file. So, like, the character would have all of the animation frames in one file. And we have our tile size, which basically says how large each block... I don't know if you can see it. Basically, the way this image is drawn is...

Refactoring Steps and UseMemo

Short description:

When starting out, it's difficult to know which parts of the codebase to refactor. Start with small changes, like adding constants. Gradually improve the codebase without needing a dedicated tech debt ticket. In the second step, we removed almost 900 lines of code and added 600. UseMemo and useCallback can be powerful but dangerous, so test before using them. The second step focuses on standardizing components and simplifying rendering. Remove direct DOM access with Useref.

Basically, the way this image is drawn is... It consists of rectangles that are 32 pixels by 32 pixels. And this is what the Tileset refers to. And our world width and world height is basically how big our level is. So when we use them across our application, it's very easy to understand what we mean instead of using 2048. I don't know what that means. And it's very obvious that you can change that variable in one place, and it will cascade down to all of the components that use them.

Do you have any questions about this step thus far? I think someone posted a question in the chat. Oh, guys, please use the Discord chat if you can, because I can't see the Zoom chat easily. It's okay. Sorry. I'll just ask... So how do you know which parts of the codebase to refactor? I work with senior engineers and I'm the only junior on the team, and they always seem to know which parts to refactor, and I'm just there like, I don't know what to do.

That's a good question. Basically, when you're only starting out, it's probably the most difficult part of the journey, like making the first step. So let's pick... Like try to ease yourself in. Pick the smallest. Like the best of the worst, I guess, so to speak. So that's why I chose just adding constants. This PR seems very big in nature. It has 14 file changes, but they're quite small throughout the board, except the first one. Sorry. This one here, right? So it only does two very small things. It adds context and it adds constants. So this is going to be one of those small steps that you take toward the right path. So to answer your question, I guess, is try to make the smallest possible change that will improve something. Okay, yeah, that makes sense. Thank you.

Alright. And yeah, you're welcome. And it's great that you brought this up. It's very important within the team when you're working on a project for a client to have this mentality within the team that if you see something, you do something. So if you're working on a feature in a file somewhere, let's say somewhere here, and you see a subpar piece of code that you think could be better, fix it. I mean, if it's okay with all of the senior devs, the tech lead, if this is a approach that is okay, it might sound like scope creep to some people, but if you try to work to improve it gradually without having to do a tech debt ticket or a tech debt sprint, if you do it, everyone pitches in, basically you're going to have a way better experience and a better code base of that. Anyone else? Moving on to step two, and actually I think I can open the Zoom chat here on a different screen, just give me a second. All right, I have it. All right, so the second step of refactoring. This is going to be a bit bigger. It has 10 files, and you can see how much code has been added and removed. So we've basically removed almost 900 lines of code, if my eyesight is good enough, and we've added 600. So basically we've thrown away 200 lines of code, give or take. So after the first step, when we've kind of delved into the world of the application, do we need to use memo? I'm sorry. I'm going to answer the question. We don't need... Use memo is one of those things just like use callback. It's powerful, but it's also very dangerous because you might end up using more memory than the application. And CPU cycles, basically to recalculate something. So you don't really need to, but you could do. So a good example for that is, let me go back to the code base. Our component which renders the whole screen. Right, it's not a whole lot of code, but it sure is taxing because what this code does is it goes through all of the screen and it renders everything that you see on the screen apart from like the fire, et cetera, the dynamic stuff. So we could use Memo for that, but I prefer to not optimize before I see a performance hit or something like... If the app behaves strangely, if it takes too much memory, basically before using any stuff like that, try to run a test before and after and see if you need it because use Memo might use more memory, as I said. And in most of the times, it's probably not worth it. And it's not just related to context. If you don't want to re-render something or recalculated, maybe the best idea is to yank it out of the context or split your context into two separate ones, cool, got it. So let's get back to our second step. You advanced to level two, Green, well, congratulations. So this is a bigger one. So after our first step, we kind of had a grasp of where the improvements could be done. And our components are kind of here and there. Some components accept a position property, like left and top, X and Y. Some don't. So some have a default position. Some don't. So our first step here is to standardize our components. The ones that have the similar behavior and within this app, a lot of them have the same concept that they're doing, they're rendering a piece of image. Now we're trying to simplify the rendering. A lot of it is really repeatable code and try to remove some of the direct DOM access with Useref, the most widely-used use case for Useref, but not the only one, we'll see it in the future.

Cleaning Up Code and Applying Types

Short description:

We're cleaning up the code, making it more maintainable and controlled. We're applying types to functional components and replacing get element by ID with use ref. This simplifies rendering and avoids copy-pasting code. The components now look cleaner and easier to understand. We draw an image based on state. Let's look at a component with animation. Multiple stores in Redux are possible but not necessary. We can connect separate stores if needed. Personally, I prefer simpler solutions over Redux.

And we're gonna clean up the code a little bit. Feel free to read through all of this. I don't wanna sound robotic and just read a bad time story for you here. I mean, this is for all of you. If you're interested enough, after the workshop, to kind of look at it, and these were just notes from me.

So let's look at the code. I think this is always a better way to show something. So, seller door, good example. Now we can define its position that we want instead of hardcoding it within the component. That means that for all of these components, we can just go to one place and say, when my game starts, I want these objects to be here or here and it's easy to maintain and control. It could be further improved by having constants. For instance, it really depends on your specific approach. And one thing that I wanna note is, this is very game specific sort of, and the details that I'm gonna go into are specific to this project. But try to take away the concepts themselves, right? So try to think of the project that you're working on or will start working on, and try to think how you would apply the same stuff, similar stuff, to your own work. Try to think it in that way. That's gonna be very helpful throughout your career. Don't think of like, okay, so he's adding left and top, okay, so that's why I'm not gonna use that in my project, because I don't have positioning. Don't.

All of the components are gonna be basically brought as close to each other and made as similar as possible. So that means we're gonna type them the same way, the functional component, they're gonna have pretty much the same props, at least as much as possible. And we could improve this later on, we could have a type in TypeScript, right? That has top and left, and then we could expand it by adding a third optional. So these are all of the steps that we're gonna take in the future. And right now, as I said, we're just making really small steps toward a brighter future. So one of the... I'm just gonna go through one of them because they're all really, really similar. So what we've done is we've applied the types to the functional component. And if you're not using TypeScript, you can zone out for the time being. But TypeScript is probably the way to go in the modern day of age. And now we've replaced the horrible get element by ID and all of the ifs related to that with a proper use ref that we assigned to the canvas itself. So now we have a way to access our DOM element without using the native APIs. The rest is kind of the same. And we're simplifying the rendering. And the way we've simplified it is instead of writing the same code twice here, we've just wrote it once and added the conditional where it was meant to be. So a lot of this may seem to senior folks like, duh, well I would've written it that way in the first place but this is not always the case. Oftentimes, people will just copy paste existing pieces of code from one component into the other without thinking either because of huge stress they're under, tight deadlines, et cetera. So, it's still worth pointing out. So this is good enough. I mean, as one of the first steps, we will still improve it in the future but this is already better. And let me maybe switch the view so that it's, hold on just a sec. Yeah. So, this is, maybe this is gonna be better to show you the cleanliness of the code before and after. So, before we had a whole bunch of stuff that was hard to understand even after our first step and here it's already looking much better. It looks like a proper component and it's quite easy to understand what's going on. We draw an image and based on some state, we pass one value or the other. That's good enough. So, as I said, all of these components are pretty much the same. Let's maybe take a look at one with the animation. Just a second. So, to answer the question in chat, I'm not super familiar with splitting the store in Redux. In the past I've used an application where we used Redux. We did, in fact, have several stores and that worked absolutely fine. I don't see it being impossible. Okay, when you say splitting the store, you mean using different global stores, not combining the stores, but having multiple stores at once. So when you update one, it won't react to the other. When you update one, you want to update the other one? Yeah. So, you dispatch to one store and have other stores not being updated. That's it. When you say that you have multiple stores. Just a second. Okay. Oh, no. I'm sorry, was that a question or? Yeah, I just want people to be sure that what you added when you say multiple store. No, we add separate stores, yeah. Oh, okay. That are not connected to each other. But I don't see a problem with connecting one with the other. Honestly, I myself am not a huge fan of Redux, full disclosure. I still dread the times it took me to add a simple action. So I try to use something simpler most of the time. We used it back in the day when hooks were not a thing.

Introducing Use Sprite Custom Hook

Short description:

We're introducing the Use Sprite Custom Hook to remove duplicate code and simplify rendering. This hook allows you to draw sprites on a canvas by passing the necessary parameters. The Seller door and the animated coin components both use this hook, with slight differences in the arguments. By abstracting the code into custom hooks, we've significantly reduced the amount of code and made it easier to understand and test. However, there is a potential issue with the useEffect hook causing undefined behavior and race conditions, which we'll address later.

So now most of the time I would just use the context to store that sort of data and try to persist it somewhere if we needed. So going back to our code, the next one, the next component is an animated one. So it's a little bit different from the seller door, which is a static thing so far. It's still quite the same. How we've basically removed all of this code, and if this animation had 20 frames, it would have been 20 LSIFs. Once again, this is a very basic improvement. We're just using math to solve our problem and just multiplying our X position by the frame. And voila, we've just removed four, how many? I can't count. A lot of those conditionals and just put them in one call. And we're also making sure that we're cleanly resetting, clearing the interval within our component as a cleanup function from our use effect. Always do that. People tend to forget, since it's not as clear as with classes when you have component unmount or stuff like that, it's very important to clean your stuff up, especially the timing functions. They can cause a lot of undefined behaviors and race conditions. The rest is absolutely the same, except for maybe the player. Is it the player? Well, the player... the differences are so large that it's impossible to render it on first. Okay, so we're just typing it properly, trying to give the same properties. But still, it's very, very complicated to understand what's going on here. We're trying to improve little by little. In the last PR, you're barely going to be able to say that this is the same component with all of the changes. These are the only changes that we've made. I'm trying to remind myself what's going on. But basically, we've applied the same improvements as on the other components, like using the frames to improve that stuff, make fewer calls to the render. The player component at this stage is still a mess, and it's probably not even worth going into detail. Right, the only improvement is this here basically. So we've removed the conditions. I think per frame or something like that we've moved it into the, we've used the math solution from the other component here. Right, so it's time to move on. And we're going to brush... We're going to go through the PRs really, really quickly and we're going to go into more details and I'll have a couple of on your own tasks if you're interested to add to the code base. And keep asking questions if you guys have in Discord or Zoom chat, I have both open.

So the third step of refactoring and these are going to get more and more interesting as we start to add stuff that we've discussed during the intro. So we're actually creating custom hooks now, stuff that will remove a lot of duplicate code and is going to look really, really nice. So we're introducing the Use Sprite Custom Hook to those unfamiliar with game development. What Sprite is, is basically it's an image. It's a connotation from the good old days of game development in the 80s or so, maybe even before that, but it's a function, basically a custom hook, a function that you pass your canvas, basically where you want to render it to, the position where you want to render it on. And the tile set, basically the image you want to render, it's width, height and the position of the image within that, the position of the sprite within that image. Too much detail. And you can already see how much code we've removed and we've added one single function call. And if you open that component now, you look at it, okay, we have a reference to the canvas, we're drawing a sprite and we're done. I love these kinds of components, it's really great. Easy to understand, easy to test, just amazing. I know I'm complimenting my own code. I do that all the time. So this was the Seller door, a static object. We're gonna go into the animated one, the coin which is to remind you is just flipping like that, just spinning. Very, very much the same, only we use an animated sprite. So again like naming your hooks and functions so that they explain what they're doing just throughout through their name is very important. It adds context to whoever is looking at the code and to yourself in the future. So it does the same thing except we're adding a few more arguments like the animation length, basically how many frames does the animation have and how fast the animation is is going in milliseconds. And again, you can see how much code we've removed and simplified down to a single hook. And these are absolutely identical. And the awesome thing about it is like you can open any of them and you can see the same code and understand that they work in the same way and that only some of the inputs change and nothing else. This is the whole idea behind abstracting code like that. And let's look at the custom hooks. Let's start with user sprite because it's a little bit simpler. So we basically yanked out that part of the code found in all of the components into one place, wrapped it in the use effects. So whenever those props change, we're gonna do that. And if you've spotted a code smell, basically an issue with that, you can give yourself a gold star because we're creating a problem here. I will not disclose it yet. If you can understand why then good on you but we will go into detail on how to fix this. Basically, this user effect will keep on updating and it's gonna cause... It won't be an infinite loop but it's gonna cause some undefined behavior and race conditions. Let me check the chat really quickly. Yes. Anything but React is probably a better choice but there are game frameworks and game engines that are specific to web and there are engines that can do native builds and web. I myself am using Godot and it's a free and open-source game engine. That's really, really great. So again to to answer one of the previous questions like how do you know what refactor? So here's a good example. You take out a piece of code you know that it does one thing.

Rendering and Animation Separation

Short description:

When creating custom hooks, it's important to maintain a single responsibility. By separating the rendering of a sprite or image from the animation and movement, the code becomes more manageable and reusable. Let's continue to the next steps.

It renders the image or an animation and you take it out and you add it into a separate function but be careful of what it does right? If our sprite were to, if our object were to move and have an animation, removing all of this and creating a custom hook that does rendering animation and movement would be, wouldn't be that great, you know, you would have probably, you would have, you would be better if you would split those up into two separate ones. So try to, to keep it to a single responsibility like here. It just renders a sprite, an image and this one renders an animated image, nothing, there's no more to it and it shouldn't. So let's move on to our next steps.

Improper Composition and Code Reusability

Short description:

Improper composition of components in the application. The player component should not be responsible for rendering the UI and player's health. We've created constants to track the player's health and added it to the global context. The Player Health component renders the heart image based on the player's health. Removing unnecessary code from the player component and improving code reusability. The codebase will be larger but easier to work with and expand, thanks to reusable systems.

Step number four, so their besides bad code, so to speak, there's also improper composition of your components within your application and a good example of that is the players health, which I can't see here. Actually, let me show it to you really quickly. So, let me bring this. I can't see it. Let me get back to you on that. I'm gonna switch branches really quickly and show it to you then. And again, it's gonna build and it's gonna take a while. Or actually might be very fast. So, this is what I was talking about. Like this part, this UI... should not be part of the player component. It makes no sense. The player component is tasked with one single thing, is rendering this player. And maybe doubtful, but like managing its controls and health, et cetera. It should not be responsible for rendering the UI and players health in no way. So, we're gonna fix that. So, we're creating a few constants here. Where are they? Sorry. So, we're tracking the players maximum and minimum health through a constant. So, it's very easy to change and use throughout the application. We're adding the Players Health to the global context so that any component can have access to it. So, for instance, since we've yanked out the player health UI from the player, it needs that context. It no longer has access to the Player's Health. And okay, so these are bug fixes basically, so we will ignore them. So we've created the Player Health component. And you can already see that just by having those custom hooks we can already put them to action in new components. So we've used it to render the Heart here. And it's also animated based on how much health the player has it will render a different image. So I'll give you an example. I have to walk to it. Oh, my bad. So if you'll look at the corner here, if I hit the fire... If I hit the fire... Sorry. I take damage, and the heart changes the image that it draws. And now we're dead. So this is what this line does. And this is where those constants come in help, and some very basic understanding of arithmetic will really, like math, help you write less code basically. I could have added a whole bunch of conditions, or a loop, or inserted your idea of how to do that when it could have been just a simple math solution. And we've removed it from the player component. It no longer needs access to... Oh, here. To the HealthCanvas, we've removed it completely. Okay, we've removed it, and we've added it to the app itself, and now it sits beside all of the other components. It's not ideal, we will improve it later on as well, but again, it's a step in the right direction. And our PR is quite small and to the point, as only nine files changed, and the changes are even smaller. And again, you will probably start seeing the fact that we're adding more lines of code and removing fewer. And as I've alluded before, our code base is gonna be almost twice as big after the refactor than it was before, but surprise, surprise, by magic, it's gonna be much easier to work with and expand because we've created systems and code that we can reuse. And the larger the more features we're gonna add to the application, the ratio of that code and reusability is gonna skyrocket. It scales very, very, very well.

Player-specific Utility Functions and Constants

Short description:

In this step, we create player-specific utility functions and constants. These improvements make our components cleaner and more reusable. We've also introduced the concept of vectors to define the player's direction and streamline movement. The utility functions have been moved outside the player component for better organization. Overall, these changes enhance code readability and maintainability.

So the fifth step... So we're going to create some player-specific utility functions and player-specific constants. It's basically a PR that focuses on the player because at this point the rest of our components are looking pretty good. Like that small, sorry, that small improvement of using custom hooks did a lot for us. Like the other components are pretty clean and pretty reusable and we've seen just by creating the player health UI we've been able to reuse them and create something really, really fast and be certain that it's gonna work.

So if we were to use unit test for instance, if we write a single test for that use sprite or use animated sprite, custom hook, we are certain that that functionality is working. We don't have to write tests for it again and again and again for that part of the code, if we were to just have duplicate code across the components.

So let's look at the code here. So we're defining a bunch of player specific constants. I like doing that because I don't wanna pollute the global constants file because that, in its recipe for disaster, if you're gonna define all of the stuff for all of the applications in a single file, that's gonna be a mess. And even your imports are gonna be huge and lag context. If you're gonna see an import from player slash constants, then it's gonna make more sense that it relates to the players width and height instead of some abstract width or height. So as a rule of thumb, always try to add some context to your file paths, directories, your file names, and obviously constants and variables and whatnot.

So these are pretty self-explanatory based on their names, the animation length, player has three frames when he moves, how fast he moves. So if you're testing the game out and you feel like the player is walking too slow, just bump this one thing and it's gonna be way faster and easier to manage as well in the future. And we can do A, B testing. And I don't know if some play testing, somebody says it moves too fast, some say it's too slow, it's really easy to maintain. And I will show you the code that was before that related to the speed and how hard it would have been to change that and tweak that. And we have an object that basically defines our inputs. So it says that the space bar and the entry keys will allow us to interact with objects. Up and well, the rest of the directions are mapped to WASD and the arrow keys. It's a very nice way. Again, look at it as a concept, defining objects like that is very common and a lot of the times saves you a lot of code and if conditionals or switch conditions. For instance, if you replace this with an enum and there will be an example like that in future PRs. You can very easily, so to speak, automate your fetching the data from an object and you can be certain of the type if you're using an enum or something like that.

Moving on. And you can see that we've removed it from the player component, we've removed the constants into a separate file so that we can reuse the same values in the component as well as the utility functions for the player. Okay, so one more thing that I've skipped in the description. Having a understanding of basic concepts, in this case, as vectors from math, and by no means am I a math nerd I'm pretty bad at maths, but this concept allows us to define a direction, basically, saying where the player is gonna be facing. For instance, this is what we're gonna be using the vectors for. So we've created a class that allows us to do that, and we've created a bunch of static methods on it, like zero giving no direction, it's just a static, it's not moving in any way. A up function that says that the player is going up, down and so forth and so not. And this is what we're gonna be using and applying into. So instead of having, where was it? Just say down. We're gonna have vector down that's gonna be way more useful for us in the future. You will see a little bit below how we're gonna use that concept to remove code and streamline the idea of movement. Because it's very, very important to not write code the way you think of it. If you think, okay, the player is moving down, then I have to, let's say, add to the Y position the speed. You're gonna end up with very verbose code that is very hard to manage. Instead, try to apply a concept to it like vectors in our case. And please come up with such ideas for your own project as well.

So, let's actually, I just wanna find that one piece of code with the vector right here. So this is another function. We're gonna go through the functions in just a second. But basically the idea is that we're simplifying all of this stuff here and here into a single function call that says get the direction from the input. So if you press the W key, or the arrow keys, it's gonna tell and set the direction to a specific vector pointing in that direction. And we're just gonna use that as an input for our move function. And from get-go, when you first open this file, you're gonna see, okay, so we're getting the movement direction and we're moving the player in that direction. It's very easy to understand and grasp the concept of it and change it in the future.

So let's go through the utility functions and we have a bunch of them, but they're all very, very simple. It's basically the same code that was before in the player, just moved outside with very few tweaks. What to start with? I guess the top. Well, let me remind myself what this is. Okay. So we're using the sprite position by basically getting the proper animation position. It's game-specific, so I can just go to the next function. So the draw frame is basically drawing the same thing as it's similar to the use sprite hook. Although this is just a function in our case because we don't need hooks because we wanna change it within an interval. Paul, I'm gonna get back to your question. It's a good one. So here's our get input vector that is so helpful to us. We're basically nesting conditionals and honestly looking at it now, we don't need the else here because we're returning. But basically, based on the input constant we've defined before, we check if that object key, if you remember it's an array, includes the key that we've just pressed. Let's say the arrow key, then it's gonna give us the proper vector or direction of movement and we're just returning that. And if there's no input that we're interested in, then we're just returning no direction, no movement whatsoever. And this is the next important one, how to simplify it. Now we've simplified the movement of the player by just not doing anything if there is no movement, no direction, and then just multiplying the direction by the speed basically. So if we're moving to the left, then we say we wanna move to the left that much. And unfortunately, this horrible piece of code is just because of the...

Barrel Files and Naming Conventions

Short description:

I use barrel files to export everything from a directory, making it easier to import functions and data. The naming convention for components can vary, but consistency is more important. Having a file name that matches the component can be helpful when searching through the codebase. As for alternatives to UseEffect, I haven't come across any articles discussing them. It's important to understand the concept before making changes to a codebase.

And unfortunately, this horrible piece of code is just because of the... How I move stuff with Canvas. And I wish it was better. And that's about it. To answer your question, Paul, I really like having these. These are called barrel files. Barrel files, like as a barrel. B-A-R-E-L, I think. They're basically exporting everything from within the directory, right. So you will see a lot of these in my case. Not this one though. So why I use them is that basically it allows me to do stuff like this. I don't have to append the file name. And if I have a lot of utility functions, like here, I can do a single import of all of the functions or all of the data that is within that directory. Otherwise I would have needed three separate lines for every function. I like to do... It's just a, it's not a matter of good or bad, it's however you do it, however you like it. I choose to do that.

Yeah, it makes sense. But what I meant was I've seen this in two ways. I've seen people just writing components inside some file named exactly as the component and then using a barrel file. And I've seen in some projects, people writing code and entire component right inside the index file, inside the barrel file. Yeah, so if you look. So my name is... My name is your code basically. This is what I mean. Right, so my component is basically called the index TSX, right, within the player directory. I don't see the point of having player directory slash player dot TSX. Again, it's a preference. Whatever choice was made by a tech lead or whoever in a project. It's fine. It's really not that important. I would say that the more important thing is to have it consistent.

Oh, yeah, 100%. I was curious on your thoughts because my personal preference would be to have a file name exactly as a component because I'm using Command P to search through the codebase. Yup. So that will help me but yeah, I guess it depends on what your needs are. True. Yup, thank you. Oscar, I'm not sure what an alternative to UseEffect would be. I haven't seen any of those articles really. I would be happy to hear your thoughts on that. I mean, I can't imagine a way to cause a re-render otherwise except by just using the ref, I guess. I don't know. I might look at that talk as well. And again, this is great. I don't want to call it clickbait or anything, but if we're talking, working for companies and clients with existing codebases, if you see a talk like that, it's definitely interesting and worth your time. Don't go deleting all of the use effect in a codebase after the talk. Make sure you understand what's going on and see why it's done. Understand the concept again.

Cleaning up Code and Improving Collisions

Short description:

In this part, we will focus on cleaning up the code and improving collisions and movement. We will expand our context to incorporate collisions with different objects. We will use the 'ColliderType' class to define the collision area and type, and handle collision events accordingly. This demonstrates the flexibility of architecting components. We will also utilize refs to manipulate objects without causing unnecessary re-renders.

Alright, so this is the sixth part of the workshop. We have two more parts to cover before we can start working on our own. In this part, we will focus on cleaning up the code and improving collisions and movement. These concepts are essential in game development, but they can also be applied to other systems using concepts like vectors that we have discussed before.

We will expand our context gradually to incorporate the idea of collisions. We will create an array called 'colliders' that will define all the objects the player can interact with, such as the heart, the coin, and the fire. Each object will have different logic for collision events. For example, colliding with a fire will cause damage, while colliding with a heart will heal the player.

To achieve this, we will use a class called 'ColliderType' that defines the collision area and type. It also has a method that specifies the action to be taken upon collision. This class demonstrates the flexibility of architecting components. We can make certain actions optional or provide default arguments to handle different scenarios. Additionally, we will use refs to easily manipulate these objects without causing unnecessary re-renders.

Colliders, UseColliders, and Heart Component

Short description:

We're using refs to create colliders and move objects without causing rerenders. The ColliderType class defines collision areas and types. The overlaps function checks for rectangle intersections. We're using the UseColliders hook to add colliders to the array without mutations. Each component differs only in type. Creating a single component with conditions or parameters would be the wrong abstraction. Duplicate code is more efficient and safer for future changes. Let's look at the heart component, which now has logic for collision.

So let me check where is it. Globals, okay. Colliders here. So I'm using a ref. An array of refs basically that are a type collider and we're gonna look at it in just a bit. And we're not allowing to change the refs directly to a read only flag basically. But we allow to push to the array ourselves. And this is very important. One of those key concepts of functional programming and cleaner code is not to allow to mutate data. Don't just push to the array, like create a new array instead for instance. And we've introduced some cleanup code like instead of defining a function like that and having it and having to type it every time, we've created a very small helper called no op. No operation utility function defined here. And this is what I'm talking about. Like create really, really small utility functions, create a lot of them and use them across the board. Don't be afraid of a file like that. It's really good. It's helpful. So let's look at the more important stuff, the ColliderType. So what it does is really, really simple. It's just a class that defines a rectangle, basically the area of the collision, and the type of the collision. So it might be... the collision might be of type damage, bonus like scoring points or getting health. And then we have a method that you can pass that defines basically what happens upon this collision. And this is a good example of architecting flexible components. Let's say, there is a case when you want to catch the collision, but you don't want anything to happen. So instead of forcing the developer to pass an empty function, just make it optional and just check. Was it passed? Call it. If not, just do nothing. Or you could, in fact, just set it to no op as a... as a default argument. But in our case, we will pass arguments to it so that the function can alter the collider itself. And again, talking about systems and concepts, this is a class that defines an area of rectangle. It's very simple. It allows us to move the rectangle to or by, and it defines itself with X and Y positions. So nothing really complicated. The most important function is the overlaps one. It basically checks if one rectangle overlaps the other. And this is the core logic of a collision, of catching something intersect, one rectangle intersecting the other one. This is a bug fix. So let's get to it. OK. So I'm reminding myself there's going to be some changes here to how the colliders are set. But basically, let's look at one of the objects that can have collisions. So we're creating a ref and I'm using refs basically to move those things around really easily so I can pass them to the context I can change them without fearing that it will cause rerenders because if it will cause rerenders, these data is basically unrelated to the rendering part at all. So that's why I was saying before that you should use refs a lot if you want to move around. I guess you can call it state, or just basically data that is unrelated to the UI component, to the client-facing stuff. If this is, you can govern some logic, even business logic, even the logic of some UI components, but you don't want to rerender every time something changes here. So use refs for that, they are very helpful in that terms. So I can easily create the Collider and use the UseColliders hook that we're going to look at in just a second. And not be afraid that if this stuff changes, I'm going to cause a rerender and lose all of the progress through logic that was created before that. UseColliders. Again, it's very, very simple. We just have a, so to speak, on mount UseEffect. It's going to add the Collider object we've created to the Colliders array. It's going to make sure that it doesn't mutate it, it's going to create a new one, a new copy of it basically, and set it to that. And all of the components are the same. Absolutely, they only differ in the type. And at this point, when you're looking at coin, fire, heart, et cetera, you're thinking, okay, but why not create a single component that has some conditions or some parameters that you can pass like, let's say, with heights and the collision type. And this, my answer to that would be the wrong abstraction. It might work for a very, very basic idea, but if you look at the heart, for instance, it's logic to collision is vastly different. So is the fires and then the coins when we're gonna add features. So that would mean if you were to have a single top level component, and this is closer to inheritance and object-oriented programming, that would mean that you would have to pass all of those things to that component from somewhere, you would have to store them somewhere. And that would be a big problem, especially if you're gonna be asked to add new features and change them. That's why it's much more efficient and just plainly safer to have duplicate code to some extent. It's not huge, it's a bunch, it's a few lines, Fire and Coin, they don't differ that much. If you see me clicking through, you can actually see the data changing. But it's very easy and it's easy to understand what's going on without having to understand the whole chain of inheritance. So let's look at the heart really quickly because it's the first one we've made changes to to actually have some logic within the collision. So what I had before is when you pick up a heart, it would just disappear. And it was really, really bad because I was, I don't know if I can find it, I don't want to waste time.

Repositioning the Heart and Player Component

Short description:

Instead of removing the DOM element, I repositioned the heart when taking damage. This logic was previously within the player component, causing conflicts when making changes. Our changes have allowed flexibility to add different logic. I can easily modify the heart's behavior. Let's now focus on the player.

I was basically calling the Remove method on the canvas. So from within React, I was literally removing the DOM element, which is a big, no, no nobody should ever do that. So instead I decided like why refresh the game when I take damage, like I would probably just reposition the heart. So let me show it to you, but take damage and pick up the heart it will just show up in a new place. And that logic was written, before that logic was within the player component. So you can see the heart here now. And having it there meant that whenever we want to make changes to the heart we would have to update the player. And that's really, really bad, especially if multiple teammates are working on the same file, changing the same file in different ways that's going to cause a lot of conflicts. So with our changes that we've made up to this point, we've basically allowed ourselves to be flexible and to add logic such as this. I can easily change this. I can add a timeout to reposition. I can hide the heart completely. I can do whatever I want. So with having systems like that in place, you can start to see our work to bear fruits. And here they are. Let's look at the player.

Simplifying Collision Logic

Short description:

We've simplified the code by replacing a repetitive function with a loop. Instead of wrapping all the logic inside a condition, we return early if the condition is not met. This improves readability, manageability, and testability. We check the type of the object collided with and perform specific actions based on that. However, there is still room for improvement in the upcoming PRs.

So we have a very simple function, well actually a loop that we will create a function from later, but basically we're removing all of this nonsense that nobody in the world could relate to. What is this? It's basically a bad way of that overlaps function that we saw in the rectangle class, if you remember. This is the same thing over and over again and over again. So we've just replaced that and we're basically looping through our objects that we're colliding with and we're checking if the player's rectangle overlaps with that object's one. If it doesn't, we just return early. And this is a very good concept that I'm a huge fan of. So instead of wrapping all of your logic inside this condition, just flip the condition and return early. This is going to save you a whole bunch of indentation levels and it's going to be easier to manage and easier to test for that matter as well. And we're just checking every type. If it's a health pickup, then we set the player to plus one of his health. It's really, really simple. It's still not perfect. Far from it. The next two PRs are going to improve upon it very, very much. Let's look at the utility functions that we've created for that.

Creating Separate Utility Functions

Short description:

By creating separate utility functions for specific actions like walking and knockback, we avoid the need for a single function with extra arguments. This approach allows for clearer code and easier maintenance, as changes in one function do not affect the others. Duplicating some code in different functions may seem counterintuitive, but it ensures better control and reduces the risk of regression test issues.

So again, here's a good example. By just creating a new utility function that does only one thing instead of reusing the move function, we've created two ones. So before, we had a single move function that moved the canvas to the specified position. Now we have three. The move that the other two use and walk and knockback. So it's easy in the code in the player component to see the call to walk and understand what happens. And the knockback, same. It's basically the same idea with minor differences. So instead of having one function with extra arguments, for example, like the speed, that you could reuse the constants instead and defining the behavior very strict and to the point. And again, to emphasize the idea of duplicating some of the code, you can see it in the knockback and you can see it in the move. But it's worth it because I can change one and not worry about the other changing. So that allows me to sleep well at night without fearing that I've caused some regression test issues.

Utility Functions, Score, and Events

Short description:

We've added utility functions for the heart and the coin. The code is looking better, and we've added a score feature. The UI component now houses the player's health and score. We've reused logic for the heart to set the score based on collision. The coin is hidden for two seconds after being picked up. We've added a hide method to the collision class to achieve this. We've fixed a bug with use effect and passing objects as dependencies. The ClampValue function is used to set the player's health. The last PR introduces a cool concept of events with a lever and a cellar door.

That's about it. Actually, no. More utility functions. So again for the heart, instead of having this code, you just create a Get Random Position. Who knows? Maybe I can use it for something else. Maybe we're gonna use it for the coin. Or maybe we're gonna use it for the player. If we get a specific damage or we add a feature of casting spells, we might want to reset position to somewhere, to a random number in the world. And we've went through this already.

All right, there's two more. We're doing okay on time. So we've gone around, covered most of the things. So at this point, the code is looking way better. We're able to add new features. So I've added the score feature. So you can pick up coins, and there's gonna be a score counter here. If you use the main branch, there's no such thing. We've removed the UI from the player. We've improved it further. We've just created a UI component that now houses the player's health, as well as the score. So it's much easier to manage, and a single component encompasses a single idea and a concept. So now it holds all of the UI, basically. And it can consist of much more complex components if you want to. And we basically keep expanding our context by adding score to it. Let's look at the, okay, so again, working on top of what we've created before, we've just reused a very similar logic we've used for the heart. And we've added the same onCollision function to the collider that sets the score based on the points constant that we can easily change in the future. It hides itself for the timeout duration, so for two seconds. So if I, oh my god, it's gonna be a long walk. So when we walk up to the coin, we pick it up, it's gonna give us 10 points, it's gonna be hidden for two seconds, and then come up again, and the same and the same and the same. So if I wanted to, I could reposition the coin after we've picked it up very easily with just one single function call and be sure that it works correctly if I had Unintest for it. And the rest is very, very simple. One of the interesting solutions that I had to come up with, and this is alluding to the thinking on the spot, Silly Me thought that I could just return null if it's hidden instead of the canvas. But unfortunately, when we check the is hidden state and we return null instead of the canvas, that basically breaks the canvas reference we had before. So if you flip it back, it's still not gonna render. So instead of changing how the component works et cetera, I just went ahead and on the collision class, I've added a hide method, which basically explains the idea that I'm trying to achieve, right? The goal, the feature that I'm trying to add. I'm not really trying to remove the canvas or the reference or just hide it. All I want is to not display the coin and to not trigger collisions. And I've just added the flag to the class and I've added the check to the type of the collision. So if the collision object is hidden, then any type of check, even if it's true, it will fail and the player will ignore the collision. And this is the point, if you remember me saying that our use sprite and use animated sprite had a bug in them, when we used to have a props as input to our dependencies of use effect, basically with React, and this is very important, I learned it the hard way, and yet, again, I've made the same mistake years after. When you pass an object to use effect, even if the object is not different, if it didn't change, it is still a different object. It's a different reference to the object. It's not pointing to the same, so to speak, memory, space of memory, it's just a new copy of the same object, so to speak, without getting into much details. And that basically causes this use effect to keep on firing every time it's being called. So if there is a re-render on a component that uses animated sprite, even though it's props didn't change the use effect, and just by the nature of JavaScript, will think that it's a new set of inputs and trigger this whole new thing. And the problem that we would see is that the animation would work differently, would speed up and then go to normal speed in a lot of race conditions. So one way to fix it is to structure the whole props object. It's not pretty, but most likely your use case will not have so many props. This is, this being game specific, we have the width, the height, the position of the tile, the position in the world, the speed, the length, et cetera. So it's not too bad, it's fine. But yeah, it was a very easy fix. So let me see what else. Maybe in the utilities. Oh, yeah. Again, a very good example of a small function, but very, very useful. And one which makes us think less when we look at the code is the ClampValue function. So this is used to set the player's health when we pick a heart or even take damage. So instead of, let me find it. So instead of letting the app, instead of letting the player set the health, we're setting it ourselves within the AppComponent, within the SetPlayerHealth function so that all other components don't have to think, don't have to have any logic related to the setting of the player health. They're just passing in a number and we're doing all of the calculations for them. And I guess I've touched upon everything else here as well. So the last PR I think is, I myself think it's the coolest one. It introduced a whole cool concept of events. So I'll try to show you. Yeah, cool. So at the very top of the house, we have a lever and a cellar door. So if I press the interact button, nothing happens right? If I come close to it and press it, we can open and close the cellar door. And that's pretty cool, those two are separate components, unrelated to each other. They're siblings, they have no connection really. The only thing that they share is the same context.

Implementing an Events System

Short description:

We've implemented a simple and basic events system using React's standard library. This system connects components and eliminates the need for excessive prop drilling. By using an object with arrays of functions, we can set and call specific event handlers. The events system lives within the context and can be easily wrapped in a separate events context if needed. The events are defined in constants, allowing for flexibility and customization. The example shows how the events system is used with the seller door and lever components. The lever component sets events based on player interaction, and the seller door component handles those events. The code also demonstrates the use of refs and a custom hook for event triggering. The resulting code is more organized and easier to maintain.

So how do you connect the two without passing too many props to the app, to the global root level component and then drilling them down? So what I came up and to note without using any third-party libraries like RxJS or Redux for that matter, like everything is built from what is available as a standard library, so to speak, from React. So it's a very simple and basic events system that allows us to connect the lever and the seller door, or for that matter, any component, no matter how deep within the tree of components they are. And by doing that, we've also removed some of the state from the app component as well. And we've introduced a bunch of, well, just one custom hook and some extra features. The events by far the most interesting ones. So let's have a look what we've removed.

So we've removed the state that governed the state of the seller door, is it open or closed, and the state of the lever if it's used or not. So we've completely removed it and we've replaced it with the events system here. So what is it? It's an array of refs, basically, with, hold on, yeah. It's an object, I will show you the definition of the type a little bit later. It's basically an object, its keys are arrays of functions. We have a setEvent function, which basically sets a specific event type. It adds a specific event handler to an event type, to an array. Without modifying it directly, it just depends to it. It just creates a new copy of it. And we have a callEvent function that basically finds the specific event type in that object and calls all of its event handlers with just this one line of code here. Well, the cool thing is that we have this, so to speak, basic, complex system within the context and we make it available throughout the application. And you can say that I'm breaking my own rules that I'm kind of polluting the global context. And for this example I think it's fine, but we absolutely could have created a separate events context and just wrap the components that need it. That's very easy to achieve, like 10, 15 minutes of work. So you can see that none of that stuff is actually drilled into the components. It lives on the context itself. So let's look at the actual events and how they look. Actually, they are in constants. So events, this is the place where we define all of our events. So we have a lever on, lever off. We can have however many events you can think of. We can even have different objects defining different events and using that for different types of contexts as well. So you can be very, very flexible with that. So for instance, an example would be like a game start event and have something that would react to that in the component or even in the app itself. Let's see how we use them. Here we go. Yeah, so the seller door basically defines the handler. This is how it's used here. Within useEffect, when the component mounts, it says, I want to handle the lever on and lever off functions, events. And I'm gonna open and close myself when those events are happening, and that's all you have to do. Within the, where is it, the actual lever component, we are reacting to that. Well, not reacting, we are actually setting the events. So when the player comes, so remember, there are three steps to this, basically. In order for this to work, the player has to come to a specific point or in a specific range of the lever component and press a button. And only then should the event be dispatched. So again, since we've built a flexible system of collision, we could have used our collision to detect, to use it as an area of detection to detect if the player is close enough. So instead of defining the area of the lever limited to its image, we've scaled it up and thus created actually a new method on the rectangle class and added new arguments to it. So we're scaling by that much, by interaction range, the rectangle around the lever. So now that the player comes 16 pixels away from any edge of the lever, it's gonna trigger a collision and on collision, we're just setting the ease-on to, we're just toggling the value, right? And we have to use this notation because within the on collision function, the reactivity of ease-on is gonna be lost. This is a very important feature you have to remember if you haven't encountered this before. So instead of using useCallback, it's a safer and cheaper way to do it because useCallback uses extra memory as well. Once we're changing this, the ease-on value, we wanna trigger an event. And again, this is a good example of thinking on the spot, I would just write a simple useEffect that is dependent on the ease-on value, but there is a catch, that is being triggered on the mount of the component as well. And I don't want that, I don't want any events to trigger when the game is loaded, right? So I've created a custom hook, this is yet another example of creating systems and creating functions and custom hooks that you can reuse across the board. It just happens so that we use it only once here. And another great example of using refs. So, when the didMountRef changes, it doesn't trigger any state change in the component so no re-rendering is gonna happen. We're just tracking something, some value, some data. So in our case, when we mount or any of the inputs change, and it could be just inputs without the function, it's fine, we're just flipping the value to true, thus making it work only when our inputs change. So if our component didn't mount, false, nothing happens. This condition is false, which we toggle it to true and the next time the is on, I believe it was called, is set to true, it will actually call the event. And the proper one based on the value of is on, because in this case, reactivity is persisted. And that's how events are working. It was, I said, a basic complex example and we can reuse it across the board. And in fact, it's gonna be one of the on your own challenges for you. Let me check if I haven't missed anything. All right. And here you can see some basic cleanup of of the code like having the same variable names across the components to just make it easier switching components, not having to look for a different name of the variable. And that's basically it. That's for all of the refactors that we're done. And let's just amuse ourselves and create a new pull request and compare your refactor8 with the main branch. Yeah you can see that we've added 1,100, 1,100 new lines of code and we've removed basically half of that. But the idea is that we've created systems, systems and reusable functions that we can manage, I'm sorry we can extend our feature set, we can add new features, we can customize them. They are much easier to test so our code is much more reliable.

Improving Player Component and Code Quality

Short description:

The player component has been significantly improved, with clearer variable names and functions. The code is easier to understand and relates directly to game development concepts. The code for collision detection and handling has been simplified and made more semantic. The input loop has been optimized, and the code for movement and direction is straightforward. Overall, the code has been organized into systems, such as vectors, areas, and rectangles, and includes a collision class, event system, and utility functions. These improvements have made the code more reusable and allowed for the addition of new features. Keeping code clean and preventing future issues requires making choices based on the long-term impact on code quality, rather than prioritizing delivery or deadlines.

Potentially and theoretically that means that we're gonna have fewer bugs. And let me actually go back to the code and it wasn't quite clear within the pull requests how our code base looks. Let's look again at our best worst part of the code, the player, and we're gonna compare it. So this is now almost 150 lines of code, give or take, because of the formatting. But when I look at it, you can see that it's quite clear from even just the names of the variables, the vulnerable state, the direction, like the key press, for instance, that those are specific things, solutions to problems that are specific to game development. The current frame, et cetera. So it's more or less good already. The names of the functions that we use are clear to us. So we can see that at the very top, basically one of the first things that we do is we move the player to some position. It's clear. It's one, a single line of code. We can see that we define a function which name is checkCollision. So we understand what it does. And it's clear to us what is happening within it. That we're going for each of the colliders and checking if they overlap. If no, we do nothing, then we check every collision type and we do something with it. And this is again, one of the ways to improve it is to maybe have separate functions that bump up health or do damage. But again, like these two lines of code, they're small enough to not warrant a change or an abstraction that may cause more havoc in the future. So don't refactor or over-optimize if you don't really need it. Try to foresee, if you know, for instance, when your project that somewhere down the pipe, there's a feature that has something to do with health and how health is being managed. Maybe you can foresee some of the functions that you can create and some of the systems you can create to kind of improve this or the damage one. But besides that, it's much, much clearer. I'm not trying to say that this is the best way to do it, but it's way better and it's clear. The most important thing, it's easier to understand what is going on. And it's semantic. Like there are semantics and there is context to the names of functions and the amount of code. So we can see that the velocity is assigned to the result of a knock-back. So I can understand that the player is knocked back by some force and the player is moved to that. And the player's rectangle is moved by that force. We're being vulnerable, invulnerable at that point and we're blinking. And it relates one-to-one to what is going on in the game itself. So when you're testing your work manually and read the code it's like, okay, yeah, indeed I am blinking when I touch a fire. And this part of the code is the one that could be improved for sure. Like the loading of the image set. Again, it's one of the on your own challenges if you want to take them. But within the on key down, this is basically the input loop that is happening. It became much smaller. So we're checking for collisions, we're checking if the player's health is below We're checking if the player's health is below the minimum health. If it is, then we're setting it to, we're setting the game set to game over. If there's any interaction, that means that the enter or the space key was pressed and we don't want it handle the next piece of code because the next piece of code relates to movement and direction. And here again, direction is set to the input vector, the speed, velocity of the movement is set to how we walk, in which direction. And then the player's rectangle or the collision data is set to that velocity. And that's pretty simple. This handles the animations. And let me just quickly open the main product here. Here is a branch so that we can compare it. So the main branch was 270 lines of codes. And we've looked at it before. And just going through it right now, it just looks like some, just matrix fallback from the movies, just numbers, just letters that make no sense. Here's the one that we've created. So let me sum everything up. So what we've created within here is a few systems. We've used vectors, areas, and rectangles. We've created a collision class. We've created an event system a bunch of small utility functions that are easy to test that don't even have conditional branching. We've created custom hooks and all of that can be repackaged into a module and can be used throughout your client or your company or your team or your other personal projects. We've created a lot of reusable code that is quite agnostic to the problems that it's trying to solve. And most importantly, it allowed us to add new features like the scoring feature, like the placement, the random placement of the heart, the ability to change the speed of the player, to change the damage it takes, et cetera. It's very easy to change stuff. For instance, our health is still governed by the app, the root level app, but since we've introduced the events, we can easily remove that completely and hook up the fire, and the heart, and the player together to use events.

Okay. So basically the second part of the workshop, and we're gonna stay here until morning. It's already nine, but how do you keep that code clean? How do you not mess it up again? And how do you prevent other people from messing with it? That depends on your role in the project and in the company. But basically there is no solution, easy solution to that. And there are different solutions to different teams and different personalities. You have to make sure that you and your team understand that there are consequences to every choice that you make, every feature that you implement. When you're asked to add code, there is a question that is being asked behind the scenes. If you do it this way or that way, how is it gonna impact the code in the long run? You should always choose the quality of the code instead of choosing the delivery or the deadlines. Because you may say, okay, I have to meet this deadline for sure. I cannot skip it, but I'm gonna add some tech debt and I'm gonna fix it later.

Fixing Tech Debt and Enforcing Clean Code

Short description:

Fix tech debt as soon as it comes up. Use synonyms like streamline or maintenance to convey the idea to non-technical people. Have a vision and apply restrictions to yourself and your teammates. Work with smaller PRs to make testing and reviewing easier. Limit yourself when refactoring to avoid creating a mess. Modus Create is hiring and looking for great people. Apply on the website. Enforce clean code guidelines based on your company's needs. Have a call with your team to create a code of conduct. Ask questions and share experiences. There are no dumb questions. Share what you do on your own projects.

But in fact, what's gonna happen is you will never fix that tech debt, very unlikely. And that tech debt is gonna create more bugs and more problems, and you're gonna slow down development, if not for the next deadline for sure, the one after it. So it is much cheaper and better to fix your problems as soon as they come up. I found that depending on the client that you work or the project that you work, the phrase, the word refactor is, bears some hostile connotation to it. It's a very scary word. So try to use something else, try to use a synonym like streamline or maintenance. Something like that, it's easier to sell, convey the idea to the non-technical person. So they're not as scared by that.

You have to have a vision. Yes, we have a tech debt epic as well and we're, well, I'm very happy to work with people who share my view on writing code and it's basically one or two tickets that it never grows beyond that. We don't even add tickets to that epic anymore, we just fixed them on the fly. So it's very important to have a vision and don't deviate from it. As I've said in the very, very beginning, you're starting out with doodles and with sketches but you know that you're gonna paint Mona Lisa. So when you are in the process of actually painting all of that, don't let anything to dim your vision or cloud it, don't deviate from it, keep at it. Apply restrictions to yourself, to your teammates, to the code of conduct and how others contribute to your code. For instance, it's very healthy, I find it very good and healthy to have very harsh restrictions. Like for instance, don't use else statements even as far as that. Always use, always flip your conditions to return early from functions, things like that. But having something like that, something you can point to in your pull request reviews, be it junior developers or anybody else, if they miss something, say that this piece of line, this line of code, this piece of code linked to a readme or code of conduct and say, look at that, that has all of the resources that you need on the stuff that you did. Not wrong, not necessarily wrong, but it could be better. And try to work with smaller PRs. Like the PRs and the size of the PRs that I've showed you today, they are basically on the edge of too big. Like try to have a PR that adds one or two functions that are small. If you've created a proof of concept that needed that custom hook that I've showed you, like those custom hooks that I've showed you, add that first, add a very small PR with just one or two custom hooks, add the description that this is gonna be used in such and such way, and the next PR add it to one component then to another one, then to another one. It's much easier to test. It's cleaner, it's easier to follow. It adds a little bit of overhead but creating pull requests is quite simple. Especially if you have unit tests. Imagine you've changed 20 components to use your custom hook now and you have to update all of those unit tests. The PR is gonna be huge and really hard to review and it's gonna take awhile and it's gonna be difficult for other people as well. When you're refactoring, try to limit yourself to the scope as well and control yourself because God knows when I was doing this workshop code, I was tempted to just change everything in one go and have one or two PRs but just limit yourself because if you are touching everything at the same time, you're gonna create a mess. It's gonna be very difficult to get out of it because you will tend to forget what you've changed, where and why especially if you have an emergency. You have to stand up from your desk for a couple of hours. You come back to it and you have 20 files open all with changes. It's really difficult to come back to stuff like that. That's about it for the aftermath and our victory. So I just wanted to say that again, Modus Create is a digital agency and it allows to work with different clients and we're always hiring and expanding and we're looking for great people to work with and to help us and our clients grow and create better products and solutions. So we're always hiring and we'd be happy to see you. So come to the website, careers and apply to one of the open positions that you find suitable. And I just want to say from my heart, I've been working with Modus for over five years now, and I think it's just amazing. I love the culture there and the interesting projects and the people. We even have a bunch of people from here in the chat as well. So give it a try. See if you like it and see if it's a good fit for you. So now I just wanted to suggest some stuff that we can work on after the workshop, but I can do it after the questions, if you guys have any questions that you've been meaning to ask, but were too shy or something, just go ahead.

Maybe if I may, hi, I'm a clean code mentor at our company, and it's really hard sometimes to tell when to stop, quite too often. I see like simple negations, extracted into functions, which to my taste, it's a little bit too much. Do you have any guidelines on where to draw the line that this is too much? Yeah, sure, very often in life there is no one true solution, there is no remedy, there's no cure. It differs from company to company. If you are an advocate for clean code, and I take you you are somewhat of a tech lead as well, as your role, you have the power to enforce something. This relates to the restrictions point that I was making before. You own the code basically, right? At least until you are employed at that company or are working on that specific project. So own that, your whole team owns that code, make those restrictions and say don't do that. That is in my view and in the view of the team that detracts from the benefits of doing something like that, right? I agree with you, this is a very good case of overengineering or oversimplifying stuff just for the sake of doing that. It differs from developer to developer, but I think you would have to make those decisions for you if you find that it doesn't look right or it creates more problems or it just expands the code base so much that you have to navigate 20 files just to figure something out. Then enforce that. Have a call with your team with other tech leads or senior developers and come up with a code of conduct. And again, create the contributing guidelines or within the README or within the wiki and point people to those specific sub-sections and links saying that, we don't hate you for that, but try not to do that anymore, something like that.

Great, thanks. You got it. Anyone else? Don't be shy. Just remember there are no dumb, stupid questions. We're all different. We have different experiences and I'm open to anything. Don't be shy at all. It doesn't even have to be related to the specific code base or something like that. We're here to talk and to help each other grow and improve and that's the whole point of it. I see someone typing. Thank you. I'm actually also interested in hearing what you guys do on your own projects.

Suggestions for Improvements and New Features

Short description:

Do you find any overlap in with what I've discussed and suggested? Maybe you have cool ideas and solutions that you want to share with other people that go beyond what I've described or improve upon them. I would be interested to learn as much as you. One of my evils that I always have to fight is snapshot tests because those are doing like 6 or 7 thousand file PRs just by changing and translation and someone's mounting all the objects in the objects. We've decided to just outright ban them. No snapshots, at all. Not just for huge PRs, but also for being just unhelpful tests. When working with junior developers, work with them, and ask them questions. How do you think something can be improved, and what is wrong with this code. That helps them grow. I totally agree. This is building upon the idea of not providing solutions. When someone comes up to you with a question, question or ask of how to fix something, generally, I tend not to give the answer. I just nudge the person in the direction of how it could be improved. I give them answers because junior developers, for instance, they struggle the most with actually finding the right questions they can even Google. Anyone else? How do I remember everything? I don't know. I probably don't. It's going to be a very cool experience and very valuable experience not having something tell you what to do and actually giving more thought to all of the code that you write. About prop spreading or explicitly picking props, it depends. Most of the time I'm spreading props, especially when they are arguments in functions. I tend to use objects as arguments more often, especially the— Like, I would still use arguments if there's like two or three. But if it's more than that, I tend to use objects because you can type them clearly. I'm always a fan to write less code. If you have time or the interest to try yourself with the code base, feel free to fork it, to clone it, do whatever you want with it. One thing that I've alluded to before is using events to control the player's health. You can try connecting the fire, the heart, and the player components together and use events. You can try to fix and improve the loading of the assets. There is an NPC character that I'm going to show really, really quickly. He's a trippy character because he's changing hues and he's a weird little guy. He's not doing anything at all. So that component doesn't have any refactors applied to it, pretty much, it's quite bad. You can try to apply all of the refactors from other components to it and see how it works and add new features to it. You can try to add a start menu, maybe like a button to start the game. A pause screen maybe and options like if you want to add music. Maybe you want to try and add a retry button when the game over screen is shown so you can reset the whole state, the whole context. You can do that.

Do you find any overlap in with what I've discussed and suggested? Maybe you have cool ideas and solutions that you want to share with other people that go beyond what I've described or improve upon them. I would be interested to learn as much as you.

Well, it's me again. One of my evils that I always have to fight is snapshot tests because those are doing like 6 or 7 thousand file PRs just by changing and translation and someone's mounting all the objects in the objects. I'm totally on board with that. We've been using them for a while, but we've decided to just outright ban them. No snapshots, at all. Not just for huge PRs, but also for being just unhelpful tests. It is like, no one is going through snapshots checking that they were generated correctly. They failed, okay, I'm gonna fix something so that they don't fail. Nobody checks why they failed. Maybe they were a good failure. So we've just banned them completely, we've been forced writing actual checks and checking that specific, I don't know, attributes changed, or the values of DOM elements changed, as we've expected. Or even sometimes we say that developers don't even write UI tests at all. Like, we only write unit tests that are specific to business logic. No UI interactions, that is, so to speak, outsourced to the QA automation team. So we use different tools for that. That is a good suggestion in Discord.

When working with junior developers, work with them, and ask them questions. How do you think something can be improved, and what is wrong with this code. That helps them grow. I totally agree. This is building upon the idea of not providing solutions. Actually, it doesn't even matter if it's a junior developer or not, it's a very good life advice in general, I think. When someone comes up to you with a question, question or ask of how to fix something, generally, I tend not to give the answer. It might sound weird, but I don't give the solution right away. I just nudge the person in the direction of how it could be improved. I give them answers because junior developers, for instance, they struggle the most with actually finding the right questions they can even Google. And I've had the same experience. I've been doing full-stack development for over a decade. And doing game development has been a dream of mine for many years since my childhood. And a few years back, I started doing that, and I found myself having this baggage of knowledge for full-stack, but very little for game development. And I found myself again in the shoes of that junior developer that doesn't know how to phrase the question, what to ask. They know what is wrong, for instance, but they don't even know the words that they should use. So that is helpful to nudge them and to help them grow in that way. And that's a good learning technique because you're teaching concepts instead of facts. And if they can understand and grasp the idea behind a concept rather than just learn by heart a fact, that's gonna have a deeper impact on their career and their experience in general.

Anyone else? How do I remember everything? I don't know. I probably don't. I guess a good example is long, long ago when I was not used to the news, long, long ago when I was not using Vim and I was using, like, Sublime or something like that, I've just switched to that and it didn't have any plug-ins for any languages, basically, or I didn't know how to set them up. And basically I had to learn all of the APIs by hand, which is going to the docs and using them and just remembering them. So that's a good example. Like, remove all of the crutches that you have. It's going to be painful, but it's going to be a very cool experience and very valuable experience not having something tell you what to do and actually giving more thought to all of the code that you write. And about prop spreading or explicitly picking props, it depends. Most of the time I'm spreading props, especially when they are arguments in functions. I tend to use objects as arguments more often, especially the— Like, I would still use arguments if there's like two or three. But if it's more than that, I tend to use objects because you can type them clearly. They're just cleaner in my mind. And you can reuse the same type across different functions and just pick the ones— Like, decompose them, destructure them in the function definition. It's cleaner in my mind. I'm always a fan to write less code. If I can write fewer letters. Any more takers? If not, I will quickly transition to what I would suggest. If you have time or the interest to try yourself with the code base, feel free to fork it, to clone it, do whatever you want with it.

So one thing that I've alluded to before is using events to control the player's health. You can do that. You can try connecting the fire, the heart, and the player components together and use events. There's an example of that as we've looked at with the lever and the cellar door. You can try to fix and improve the loading of the assets, so that instead of using an image constructor and loading it that way in every component, and then hooking all of the logic within the unload callback, you can try to load all of the assets, sort of like if you played video games, maybe you've seen those loading screens, something like that, to load all of the assets when the application starts, and then remove all of the code in all of the components just to use those assets that were preloaded already. There is an NPC character that I'm going to show really, really quickly. It's a trippy character, you can barely see, he's at the very top right of the river, at the top of the river. He's a trippy character because he's changing hues and he's a weird little guy. He's not doing anything at all. So that component doesn't have any refactors applied to it, pretty much, it's quite bad. So you can try to apply all of the refactors from other components to it and see how it works and add new features to it. You can try to add a start menu, maybe like a button to start the game. A pause screen maybe and options like if you want to add music. Maybe you want to try and add a retry button when the game over screen is shown so you can reset the whole state, the whole context. You can do that.

Closing Remarks and Collaboration

Short description:

Feel free to fork the repo, create PRs, and tag me. I'll provide guidance and feedback after the workshop. Let's collaborate, learn, and share our experiences. If you have no questions, have a great day! I'm happy to have connected with you and look forward to future interactions.

I would, if you want to fork the repo and create PRs and tag me. If you want me to have a look at them, I'm totally up for it and I can maybe even give you some pointers, some guides after the workshop as well. So don't be shy in that way. So let's try to collaborate together and learn, share our experiences after the call. I'd be really happy to do that.

If you don't have any questions, I think we can wrap it up. I'll give you a couple of seconds to just jump in. All right, if no, then have a great day, or evening, or night. I was really, really happy to see you all and talk to you, and hopefully see you in pull request, reviews in the future. And maybe sometime in the future as well on different workshops.

Watch more workshops on topic

React Summit 2023React Summit 2023
170 min
React Performance Debugging Masterclass
Featured WorkshopFree
Ivan’s first attempts at performance debugging were chaotic. He would see a slow interaction, try a random optimization, see that it didn't help, and keep trying other optimizations until he found the right one (or gave up).
Back then, Ivan didn’t know how to use performance devtools well. He would do a recording in Chrome DevTools or React Profiler, poke around it, try clicking random things, and then close it in frustration a few minutes later. Now, Ivan knows exactly where and what to look for. And in this workshop, Ivan will teach you that too.
Here’s how this is going to work. We’ll take a slow app → debug it (using tools like Chrome DevTools, React Profiler, and why-did-you-render) → pinpoint the bottleneck → and then repeat, several times more. We won’t talk about the solutions (in 90% of the cases, it’s just the ol’ regular useMemo() or memo()). But we’ll talk about everything that comes before – and learn how to analyze any React performance problem, step by step.
(Note: This workshop is best suited for engineers who are already familiar with how useMemo() and memo() work – but want to get better at using the performance tools around React. Also, we’ll be covering interaction performance, not load speed, so you won’t hear a word about Lighthouse 🤐)
React Advanced Conference 2021React Advanced Conference 2021
132 min
Concurrent Rendering Adventures in React 18
Featured WorkshopFree
With the release of React 18 we finally get the long awaited concurrent rendering. But how is that going to affect your application? What are the benefits of concurrent rendering in React? What do you need to do to switch to concurrent rendering when you upgrade to React 18? And what if you don’t want or can’t use concurrent rendering yet?

There are some behavior changes you need to be aware of! In this workshop we will cover all of those subjects and more.

Join me with your laptop in this interactive workshop. You will see how easy it is to switch to concurrent rendering in your React application. You will learn all about concurrent rendering, SuspenseList, the startTransition API and more.
React Summit Remote Edition 2021React Summit Remote Edition 2021
177 min
React Hooks Tips Only the Pros Know
Featured Workshop
The addition of the hooks API to React was quite a major change. Before hooks most components had to be class based. Now, with hooks, these are often much simpler functional components. Hooks can be really simple to use. Almost deceptively simple. Because there are still plenty of ways you can mess up with hooks. And it often turns out there are many ways where you can improve your components a better understanding of how each React hook can be used.You will learn all about the pros and cons of the various hooks. You will learn when to use useState() versus useReducer(). We will look at using useContext() efficiently. You will see when to use useLayoutEffect() and when useEffect() is better.
React Advanced Conference 2021React Advanced Conference 2021
174 min
React, TypeScript, and TDD
Featured WorkshopFree
ReactJS is wildly popular and thus wildly supported. TypeScript is increasingly popular, and thus increasingly supported.

The two together? Not as much. Given that they both change quickly, it's hard to find accurate learning materials.

React+TypeScript, with JetBrains IDEs? That three-part combination is the topic of this series. We'll show a little about a lot. Meaning, the key steps to getting productive, in the IDE, for React projects using TypeScript. Along the way we'll show test-driven development and emphasize tips-and-tricks in the IDE.
React Advanced Conference 2021React Advanced Conference 2021
145 min
Web3 Workshop - Building Your First Dapp
Featured WorkshopFree
In this workshop, you'll learn how to build your first full stack dapp on the Ethereum blockchain, reading and writing data to the network, and connecting a front end application to the contract you've deployed. By the end of the workshop, you'll understand how to set up a full stack development environment, run a local node, and interact with any smart contract using React, HardHat, and Ethers.js.
React Summit 2023React Summit 2023
151 min
Designing Effective Tests With React Testing Library
Featured Workshop
React Testing Library is a great framework for React component tests because there are a lot of questions it answers for you, so you don’t need to worry about those questions. But that doesn’t mean testing is easy. There are still a lot of questions you have to figure out for yourself: How many component tests should you write vs end-to-end tests or lower-level unit tests? How can you test a certain line of code that is tricky to test? And what in the world are you supposed to do about that persistent act() warning?
In this three-hour workshop we’ll introduce React Testing Library along with a mental model for how to think about designing your component tests. This mental model will help you see how to test each bit of logic, whether or not to mock dependencies, and will help improve the design of your components. You’ll walk away with the tools, techniques, and principles you need to implement low-cost, high-value component tests.
Table of contents- The different kinds of React application tests, and where component tests fit in- A mental model for thinking about the inputs and outputs of the components you test- Options for selecting DOM elements to verify and interact with them- The value of mocks and why they shouldn’t be avoided- The challenges with asynchrony in RTL tests and how to handle them
Prerequisites- Familiarity with building applications with React- Basic experience writing automated tests with Jest or another unit testing framework- You do not need any experience with React Testing Library- Machine setup: Node LTS, Yarn

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Advanced Conference 2022React Advanced Conference 2022
25 min
A Guide to React Rendering Behavior
React is a library for "rendering" UI from components, but many users find themselves confused about how React rendering actually works. What do terms like "rendering", "reconciliation", "Fibers", and "committing" actually mean? When do renders happen? How does Context affect rendering, and how do libraries like Redux cause updates? In this talk, we'll clear up the confusion and provide a solid foundation for understanding when, why, and how React renders. We'll look at: - What "rendering" actually is - How React queues renders and the standard rendering behavior - How keys and component types are used in rendering - Techniques for optimizing render performance - How context usage affects rendering behavior| - How external libraries tie into React rendering
React Summit Remote Edition 2021React Summit Remote Edition 2021
33 min
Building Better Websites with Remix
Remix is a new web framework from the creators of React Router that helps you build better, faster websites through a solid understanding of web fundamentals. Remix takes care of the heavy lifting like server rendering, code splitting, prefetching, and navigation and leaves you with the fun part: building something awesome!
React Advanced Conference 2022React Advanced Conference 2022
30 min
Using useEffect Effectively
Can useEffect affect your codebase negatively? From fetching data to fighting with imperative APIs, side effects are one of the biggest sources of frustration in web app development. And let’s be honest, putting everything in useEffect hooks doesn’t help much. In this talk, we'll demystify the useEffect hook and get a better understanding of when (and when not) to use it, as well as discover how declarative effects can make effect management more maintainable in even the most complex React apps.
React Summit 2022React Summit 2022
20 min
Routing in React 18 and Beyond
Concurrent React and Server Components are changing the way we think about routing, rendering, and fetching in web applications. Next.js recently shared part of its vision to help developers adopt these new React features and take advantage of the benefits they unlock.In this talk, we’ll explore the past, present and future of routing in front-end applications and discuss how new features in React and Next.js can help us architect more performant and feature-rich applications.
React Advanced Conference 2021React Advanced Conference 2021
27 min
(Easier) Interactive Data Visualization in React
If you’re building a dashboard, analytics platform, or any web app where you need to give your users insight into their data, you need beautiful, custom, interactive data visualizations in your React app. But building visualizations hand with a low-level library like D3 can be a huge headache, involving lots of wheel-reinventing. In this talk, we’ll see how data viz development can get so much easier thanks to tools like Plot, a high-level dataviz library for quick & easy charting, and Observable, a reactive dataviz prototyping environment, both from the creator of D3. Through live coding examples we’ll explore how React refs let us delegate DOM manipulation for our data visualizations, and how Observable’s embedding functionality lets us easily repurpose community-built visualizations for our own data & use cases. By the end of this talk we’ll know how to get a beautiful, customized, interactive data visualization into our apps with a fraction of the time & effort!