Refactoring
In this section, we'll discuss why refactoring is important, and how to do it.
Why Refactor?
We want to list a few reasons why refactoring is essential:
-
The first lines of code you write are usually not the best. You'll learn more about the problem you're solving as you go and find better ways to solve it.
-
Refactoring is the process of improving your code by writing it in a better way.
-
Refactoring is a way to improve the design of your code. It's a way to make your code more readable, maintainable, and extensible.
-
We always read more code than we write. So, writing code that's easy to read and understand is important.
Now, of course, the topic of refactoring is vast. There are entire books written about it. So, we just covered the bare minimum here. In our case, we'll focus on our code to make it more readable, maintainable, and extensible and make some performance improvements.
Let's see our current file that contains the entire code for the Todo App
Honestly, this is not bad code, and it seems easy to read with around 62 lines of code. But we can still improve it. Here are a couple of things we find that can be improved:
-
The
Tasktype is defined inside theAppcomponent. This is not a good practice. We should move it outside the component. -
The input has a good amount of logic that can be extracted into a separate component.
Let's do these two things first. Of course, refactoring is a continuous process. So, we'll keep improving our code as we go.
Moving the Task type outside the App component
Move the Task type outside the App component. We'll create a new file called types.ts inside the src folder and move the Task type there. We'll also move the Priority type there as well.
Export the Task type from the types.ts file, and import it in the App component.
Our main file is already 10 lines shorter. Let's move on to the next step.
Extracting the input into a separate component
Create a new TaskInput.tsx file inside the src folder. We'll move the input and the respective label into this file.
Of course, this won't work because we're using taskName, setTaskName, and onInputKeyDown inside the Input component. We'll pass these as props to the TaskInput component.
Let's use the new TaskInput component in the App.
Honestly, this is not a huge improvement at all. We just abstracted the label and input it into a separate component. Also, the number of lines has barely changed.
But let's have a good look at our components. Can we move the button to our new TaskInput component? Let's try that.
We'll also pass the onAddTask function to the TaskInput component.
We are getting there. Let's also think of a better name for the TaskInput component. We'll rename it to AddTask. Also, we'll rename to file to AddTask.tsx.
Our App component must also be updated with the new name.
Now, we can also move the onInputKeyDown function to the AddTask component as it just calls the onAddTask function. Also, let's update the props accordingly.
We'll also update the App component accordingly.
Now we can see some reduction in the number of lines. Now, there is one more refactoring we can do that would improve our app's performance.
Let's see the performance problem in our app
Put up a console.log inside the App component and see how many times it's being called when we type something in the input.

Why is the App component being rendered so many times?
It all has to do with where our state is located. Our app has two states: tasks and taskName. The respective state is updated at different places in our app. The tasks state is updated inside the onAddTask function, and the taskName state is updated inside the setTaskName function.
We could argue that the App component should be rendered when the tasks state is updated but not when the taskName state is updated. And we're right. But React doesn't know that. React will re-render the App component whenever any state inside the App is updated. And that's why the App component is being rendered so many times.
But we know the taskName state is only used inside the AddTask component. So, we can move the taskName state inside the AddTask component. Let's do that.
We had to update our types as the App component now needs to know what task the AddTask component sends.
Let us also clean the App component.
Try typing something in the input now. You'll see that the App component is not being rendered anymore unless a new task is added, which is expected.

We want to mention that this is not a huge performance problem. But it's a good example of improving our app's performance without fancy tools like useCallback, useMemo, etc. Even those tools won't help us much in this case.
Oops, We seem to have broken something
If you try to add a new task now, you'll see that the input is not being cleared. The taskName state is now inside the AddTask component. So, we need to clear the input inside the AddTask component.
Though we saw this issue visually, we could have also caught it by running our existing tests. Some of our functionality might not always caught visually. So, it's always a good idea to run our tests after refactoring (in fact, have it running while refactoring too).
Let's run our tests.

We can see that the Input Clear Test is failing. Let's fix it.
We are proxying the onAddTask function inside the handleAddTask function. This enables us to add more logic to the handleAddTask function, clearing the input and passing the trimmed task to the parent without affecting the onAddTask function. This also cleans up the App component.
Our tests are passing now, and we have a much cleaner code.
Using the Form to contain the input and button
A few folks in one of our webinars pointed out that we could have used a form to contain the input and the button. This is a good idea. As it will help eliminate the need for the onInputKeyDown function. Let's do that.
Let's do that now.
We removed the onInputKeyDown function and the onKeyDown event listener from the input. We also wrapped the label, input, and button inside a form element. Our tests will still pass, but this approach has an issue. The form will be submitted when the button is clicked, and the browser will refresh. We don't want that. We want to prevent the form from submitting. We can add an onSubmit event listener to the form and stop the default event.
We added an onSubmit event listener to the form. We also added the required attribute to the input. This will prevent the form from submitting if the input is empty. We also updated the handleAddTask function to accept the e argument of type React.FormEvent<HTMLFormElement>. We also called the preventDefault method on the e argument to prevent the form from submitting.
We saw how good tests can help us maintain good code quality. We also saw how to improve our app's performance by moving the state to the right place.
This was a straightforward example of refactoring. But we hope you have an idea of how to refactor your code.
This is the end of this section. In the next section, we'll discuss component composition.
At this point, your code should be a good match to the branch of the repository: 7-refactoring
Last updated on