How to Think Like a Developer
Published Dec 24, 2020
Table of Contents
- Introduction
- Disillusion
- The Project
- Breaking Down Problems
- Requirements
- Getting Started
- The User Input
- Reading the Value
- Thinking in Data Structures
- See How it Looks First
- Displaying Todos
- Wishful Thinking
- Pseudocode
- Create Todo
- Update Todo
- Delete Todo
- Finding Project Ideas
- Reading Documentation
- Finding Help
- Conclusion
Introduction
You learned JavaScript. Maybe you watched a course. You felt confident in your abilities. The voice is gone. Thereâs silence. You open your editor with a blank stare. You feel lost. What now?
Source code on GitHub.
Disillusion
Have you watched a cooking show? The chef makes it look easy. Behind the scenes thereâs a lot of people responsible for that. Someone needs to scrub the pans, taste test, and style the food before they present it to you. Tutorials are like a cooking show where they can set unrealistic expectations, and make you feel like you donât get it. They give you the false impression the person knows everything. Youâll be relieved to know â they donât! Theyâre just great cooks.
Donât compare yourself with the experience of someone else. It takes time and practice.
âTalent is a pursued interest. Anything that youâre willing to practice, you can do.â â Bob Ross
You might have heard well intentioned things such as âjust go work on projectsâ. Itâs easier said than done. Where do you start?
These are tools, and techniques for your mind to reason about code. I want to give you confidence. Instead of feeling stuck and frustrated, youâre going to get excited about learning.
The Project
I like the idea behind the TodoMVC project. Weâre going to create a similar todo app. The styles are going to be simple. Theyâre included in the source code, so we can focus on whatâs important.
Breaking Down Problems
I want you to think for a second. How would you build something like this? The answer may be different based on your experience. We often look at things like theyâre a black box. âI donât understand it, so I shouldnât botherâ. Everything is just a sum of it parts. We can break it down into managable pieces.
A todo app is a simple create, read, update, delete (CRUD) application. Itâs the basis of everything. Think of huge sites like Reddit, Instagram, Facebook, or Twitter. Itâs just a CRUD app with extra sauce.
A todo app has all the right fundamentals. Regardless of language, or framework youâre learning. Itâs a great barometer that shows you how it works.
Your first impulse might be opening your editor and starting to code something up. Thatâs a frequent mistake. Step back for a moment and think of the requirements.
Requirements
- Input field for the todo
- Displaying the todos
- Create todo
- Update todo
- Delete todo
Thatâs it! Nothing crazy. Notice how weâre missing the read part of CRUD. Weâre just going to use an object to keep it simple. We can treat it as our database.
Getting Started
The best way of learning is through doing. You can use Codepen to get started.
Avoiding mental hurdles of having to open an editor, and having a quick way to hack on something is great, because it encourages us to play and learn.
The User Input
Letâs just add an input.
<input
type="text"
class="todo-input"
placeholder="What needs to be done?"
data-input
/>
Data attributes allow us to store extra information on HTML elements in the form of data-*
where star could be named anything.
I prefer to use data attribute to hook into things with JavaScript over id that should be unique, or class because class names could change since theyâre used for styles.
Reading the Value
Say we donât know anything about the DOM (Document Object Model). The DOM might be a metaphor for something else youâre having trouble with. You might not even known how to select the element itself.
Are we just stuck? You might be tempted to watch a video. Iâve even seen people drop everything, and go spend weeks on a course. Theyâre both valid ways to learn.
Letâs practice troubleshooting instead. We can always go the path of least resistance if we donât understand the answers. I find bad documentation is the leading cause in why we avoid doing it.
We can just tell the search engine what we want. In this case âHow do I select an element in JavaScriptâ, or âHow do I get the value from an input field in JavaScript?â. You also donât have to be that verbose. The search engine isnât human. Use keywords such as âJavaScript select elementâ, or âJavaScript input valueâ.
Youâll often find great Stack Overflow threads. The only caution is try finding a recent one since the language evolves. This is not just true for JavaScript.
Select the input element:
const inputEl = document.querySelector('[data-input]')
const inputValue = inputEl.value
Youâre actively learning, and problem solving just like youâd do on the job. This makes it stick.
Thinking in Data Structures
Looking at a problem might feel the same as looking at abstract art. Thereâs confusion around how we display, and update the data based on some user action. It would be easier if we had a place to store the state
of our app.
It could hold our todos. For example we can keep track if theyâre completed so we can filter
them later based on criteria. We can keep track how many there are by using the length
property.
A state for our todos might look like this:
let todos = [
{
id: 1,
todo: 'Refund Cyberpunk',
completed: false,
},
{
id: 2,
todo: 'Pre-order KFConsole',
completed: false,
},
]
We can quickly model the state of our app how we want. I hope you see how coding can be creative. Thereâs no rigid rules how this should work. Itâs just a concept.
This is also known as having a single source of truth. Youâre going to hear this one a lot.
See How it Looks First
Itâs easier to see how things should look first. After that itâs easier to translate to code. I always do this first. When itâs complete, you can remove it. This way you always have a reference to what youâre doing.
Our HTML for the todo list might look something like:
<div class="todos" data-todos>
<ul>
<li class="todo" data-todo="1">
<input class="todo-checkbox" type="checkbox" />
<label class="todo-label" data-todo-label>Refund Cyberpunk</label>
</li>
<li class="todo" data-todo="2">
<input class="todo-checkbox" type="checkbox" />
<label class="todo-label" data-todo-label>Pre-order KFConsole</label>
</li>
</ul>
</div>
Note: Iâm keeping the markup to a minimum. You should be more conscious when working on your own projects, so itâs accessible.
Displaying Todos
The next thing to do is loop over our todo list items and display them:
const todosHTML = `
<ul>
${todos.map(({ id, todo }) => {
return `
<li class="todo" data-todo="${id}">
<input class="todo-checkbox" type="checkbox">
<label class="todo-label" data-todo-label>${todo}</label>
</li>
`
}).join(' ')}
</ul>
`
Instead of creating the elements using document.createElement
weâre using template literals to keep it simple.
Here Iâm just picking values I want being id
, todo
from the todos
object. The join
is required because map
returns an array, which we need to turn into a string. For example ['Todo', 'Todo'].join(' ')
would return 'Todo Todo'
.
Always console.log
things to see what they are.
Hereâs how we grab the output, and display todos inside it:
const inputEl = document.querySelector('[data-input]')
const todoListEl = document.querySelector('[data-todos]')
const todosHTML = `
<ul>
${todos.map(({ id, todo }) => {
return `
<li class="todo" data-todo="${id}">
<input class="todo-checkbox" type="checkbox">
<label class="todo-label" data-todo-label>${todo}</label>
</li>
`
}).join(' ')}
</ul>
`
todoListEl.innerHTML = todosHTML
Itâs tempting to want to break things into neat functions and doubting if what youâre doing is right. Donât. Focus on making your code work first. You can improve it later.
Great job! I hope everything so far makes sense.
Wishful Thinking
Youâre close to an artist in the way which you can express yourself through code. You can make up anything, and make things work the way you want. Thereâs no right or wrong. Instead of clean code, focus on working code. Youâre always going to learn, and cringe the month later at your old code.
âWishful thinkingâ is a great practice where you write code such as a function like it has already been done, and you return later to implement it.
For example updateUI
where it clears the todo input field, creates an updated âtemplateâ of todos to display in the browser that we can use anywhere we need to update the user interface. How cool is that? You just made that up from your mind đ¤Ż
function updateUI() {
inputEl.value = ''
const todosHTML = `
<ul>
${todos
.map(({ id, todo }) => {
return `
<li class="todo" data-todo="${id}">
<input class="todo-checkbox" type="checkbox">
<label class="todo-label" data-todo-label>${todo}</label>
</li>
`
})
.join(' ')}
</ul>
`
todoListEl.innerHTML = todosHTML
}
Pseudocode
Another great technique is writing pseudocode to help us think through a problem. While pseudocode by definition is âa notation resembling a simplified programming languageâ â we can just write comments.
function addTodo() {
// do nothing if input is empty
// create a todo object with id, todo, completed
// push the todo to todos
// update the user interface
}
Create Todo
Letâs implement it:
function addTodo() {
if (!inputEl.value) return
const todo = {
id: todos.length + 1,
todo: inputEl.value,
completed: false
}
todos.push(todo)
updateUI()
}
First we check if the input is empty, if so we donât do anything. We only have to grab the latest input value, and push the same formatted object to our list of todos
. We get the id
by looking at how many todos we have. After we modify our state, we pass whatever current todos
are to updateUI
to update the user interface.
Letâs make it so that our todo gets added when the user presses enter. We already have the reference to inputEl
.
Letâs add a keypress
event listener:
inputEl.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
addTodo()
}
})
Thatâs it!
Update Todo
Itâs not enough to just read something once, and think you get it. It takes deliberate effort to try it out yourself until you understand it.
Since weâre creating elements on the fly, itâs not that straightforward to add event listeners to them. We have to add them after theyâve been created.
We could loop over each element to attach event listeners, but itâs easier to use event delagation.
Itâs great when you have a lot of elements. Event delegation is having a listener on the parent, instead of itâs children. We can determine what todo item we clicked on.
For editing the todo:
- I want the update input to be hidden by default
- When we click on the button I want it to hide the todo input, and show the update input
- Update the todo input text as we type, so after weâre done we can see the change
- If we click outside the update input, or it loses focus weâre done editing so we hide the update input, and show the todo input
- Update
todos
state
You can see this in the finished version. Itâs similar to TodoMVC. Only Iâm avoiding making it look fancy, so we donât have to think about the styles. When we break things down, everything is a lot more managable.
Letâs add an input, and update button:
function updateUI() {
inputEl.value = ''
const todosHTML = `
<ul>
${todos
.map(({ id, todo }) => {
return `
<li class="todo" data-todo="${id}">
<input class="todo-checkbox" type="checkbox">
<label class="todo-label" data-todo-label>${todo}</label>
<input class="todo-update" style="display: none" data-update-input />
<button class="todo-update-toggle" data-update-btn>âď¸</button>
</li>
`
})
.join(' ')}
</ul>
`
todoListEl.innerHTML = todosHTML
}
Iâm adding inline styles here for simplicity. In the final version theyâre located in the stylesheet for .todo-update
styles.
Our goal is to be able to click on the button, and grab the reference to the id
, along with the value of the update input that we can send to updateTodo
thatâs going to accept todoId
and updatedTodoValue
as arguments so it updates our state and user interface.
The event handler has to come after we update state:
function updateUI() {
inputEl.value = ''
const todosHTML = `
<ul>
${todos
.map(({ id, todo }) => {
return `
<li class="todo" data-todo="${id}">
<input class="todo-checkbox" type="checkbox">
<label class="todo-label" data-todo-label>${todo}</label>
<input class="todo-update" style="display: none" data-update-input />
<button class="todo-update-toggle" data-update-btn>âď¸</button>
</li>
`
})
.join(' ')}
</ul>
`
todoListEl.innerHTML = todosHTML
todoListEl.onmouseover = () => {
// ...
}
}
Weâre using an event handler element.onmouseover
, instead of an event listener element.addEventListener('mouseover', callback)
so we donât have to clean it up each time doing element.removeEventListener('mouseover', callback)
. And the reason behind why mouseover
is so we can immediately set things like event handlers.
Itâs going to give us access to the event
object which we get event.target
from. To know which todo the user clicked on, weâre going to use the closest
method to get the parent element. With that we can query and element inside, and do whatever. If youâre confused at any point, remember to console.log
everything.
function updateUI() {
// ...
todoListEl.onmouseover = ({ target }) => {
// get the closest parent which is going to be <li>...</li>
const parentEl = target.closest('[data-todo]')
// if there's no parent do nothing
if (!parentEl) return
// get the id by accessing the data attribute value
// for example data-todo='1'
const todoId = +parentEl.dataset.todo
// query the label, input, button elements
const labelEl = parentEl.querySelector('[data-todo-label]')
const inputEl = parentEl.querySelector('[data-update-input]')
const btnUpdateEl = parentEl.querySelector('[data-update-btn]')
// when we click the pencil show the update input
// and hide the todo label
btnUpdateEl.onclick = () => {
labelEl.style.display = 'none'
inputEl.style.display = 'inline'
// places the cursor in the input
// for a greater user experience
inputEl.focus()
}
// update the todo label to match the change
inputEl.onkeyup = () => {
labelEl.innerText = inputEl.value
}
// when the input loses focus hide the update input
// and show the todo label again
inputEl.onblur = () => {
labelEl.style.display = 'inline'
inputEl.style.display = 'none'
// clear the input
inputEl.value = ''
// update todos state
const updatedTodos = updateTodo(todoId, inputEl.value)
todos = updatedTodos
}
}
}
Take a stretch! Let it sink in. Another great advice is if you canât figure out something, take a break. Your brain is going to work on the problem in the background.
Iâm going to explain how updateTodo
works:
function updateTodo(todoId, updatedTodoValue) {
return todos.map((todo) => {
if (todoId === todo.id) {
return {
...todo,
todo: updatedTodoValue,
}
}
return {
...todo,
}
})
}
Letâs break it down. To update a todo item we need to know which todo we need to update. We need to pass it the todoId
, and updatedTodoValue
to replace the old todo.
Focus at the problem at hand. Take what you donât understand, and isolate it in a separate environment.
let todos = [
{
id: 1,
todo: 'Refund Cyberpunk',
completed: false
},
{
id: 2,
todo: 'Pre-order KFConsole',
completed: false
}
]
How do we update this? We not only have to update the todo, but keep the rest. Letâs just hardcode the id
so we know it works.
todos.map(todo => {
if (todo.id === 1) {
console.log(todo)
}
})
Alright, we get our todo! Now we need to update the todo
in our object:
todos.map(todo => {
if (todo.id === 1) {
return {
todo: 'Edited todo'
}
}
})
But notice the object structure changed:
[
{
todo: 'Edited todo'
}
]
What we have to do is keep the other keys on the todo object such as id
and completed
, and just modify the todo
key:
todos.map(todo => {
if (todo.id === 1) {
return {
...todo,
todo: 'Edited todo'
}
}
})
We can achieve this by using the spread operator.
[
{
id: 1,
todo: 'Edited todo',
completed: false
}
]
The order of the keys youâre going to see in the output is going to be sorted alphabetical. Weâre not done yet! You might have noticed in the output that our other todo item is undefined
. Itâs a similar problem, weâre not saying it to include the rest.
todos.map(todo => {
if (todo.id === 1) {
return {
...todo,
todo: 'Edited todo'
}
}
return {
...todo
}
})
Letâs repeat:
- We loop over each
todo
item - Modify the one that matched our
id
and return it - Return the rest of the
todo
items
To make it even more clear to someone else reading the code we could write it as:
todos.map(todo => {
let editedTodo = {}
if (todo.id === 1) {
editedTodo = {
...todo,
todo: 'Edited todo'
}
}
return {
...todo,
...editedTodo,
}
})
Keep in mind, the order in which we return them is important!
Delete Todo
Great job! This part is going to be easy in comparison. We just need to pass in the todo id, and filter the todo items. Then update the user interface.
Letâs update our âtemplateâ with a delete button:
function updateUI() {
// ...
const todosHTML = `
<ul>
${todos
.map(({ id, todo }) => {
return `
<li class="todo" data-todo="${id}">
<input class="todo-checkbox" type="checkbox">
<label class="todo-label" data-todo-label>${todo}</label>
<input class="todo-update" style="display: none" data-update-input />
<button class="todo-update-toggle" data-update-btn>âď¸</button>
<button class="todo-delete" data-delete-btn>â</button>
</li>
`
})
.join(' ')}
</ul>
`
// ...
}
Next we need to query the delete button, and give it an event handler:
function updateUI() {
// ...
todoListEl.onmouseover = ({ target }) => {
// ...
const btnDeleteEl = parentEl.querySelector('[data-delete-btn]')
btnDeleteEl.onclick = () => deleteTodo(todoId)
// ...
}
}
Lastly, this is how deleting a todo works:
function deleteTodo(todoId) {
const filteredTodos = todos.filter(todo => todoId !== todo.id)
todos = filteredTodos
updateUI()
}
We didnât have to write it out first. We used âwishful thinkingâ.
Great job, weâre done! đ
Finding Project Ideas
Here are some resources:
- JavaScript 30 â Build 30 things in 30 Days
- 100+ Javascript Projects Ideas
- Practical JavaScript Project Ideas
- Vanilla Web Projects
You donât have to master JavaScript. Do a couple of projects to get confident. That way you get an idea what problems frameworks solve.
Reading Documentation
Donât be afraid of reading documentation. The MDN Web Docs are your best friend if you want to learn how things work. Letâs say you want to learn about the array method map.
Sometimes reading documentation can be tricky. Take for example the syntax examples MDN gives you.
let newArray = arr.map(callback(currentValue[, index[, array]]) {
// return element for newArray, after executing something
}[, thisArg])
The first line with the brackets might be confusing. Itâs not JavaScript. It just means those index
, array
parameters are optional including thisArg
.
This might look intidimating, but if we look at the examples we donât use half of the arguments. It just informs us what map
accepts.
[1, 2, 3, 4].map(number => console.log(number))
When documentation isnât helpful, you can look at code on GitHub. Great projects also tend to have an examples folder in their repository. Write down questions for each line of code you donât understand.
Finding Help
Youâre going to encounter code you donât understand all the time. Take what you donât understand, and research it. If you donât know what the piece of code is doing you can ask on Discord. Hereâs a list of awesome discord communities. You donât need to ask for permission to ask questions. Just be polite, and maybe read the guidelines of the server before you do.
Conclusion
Donât think you understand something just because you watched it.
If you donât understand something, practice doing it. Let me explain. You donât want to practice remembering syntax. You can look that up. Practice the steps in your mind. Breaking things down into managable chunks. Your code could look completely different, but if it works be proud.
As a challenge you can display how many uncompleted todos are left, add the ability to filter by active, completed, and all. You could also learn the localStorage API so you can preserve them through page reload.
Even the most complicated piece of code starts with a single line of code. This is how to get into the mindset of a developer.
Thanks for reading! đď¸