Highlight key points in articles
This commit is contained in:
@ -15,15 +15,15 @@ SEO is becoming increasingly relevant as the internet keeps growing. While most
|
||||
|
||||
### Craft user-friendly URLs and map the appropriately
|
||||
|
||||
First and foremost, start at the very beginning, which is your URL slugs. For URL slugs the rule is that you want them to be human-readable, with words separated by dashes and with no random letters or digits added. Secondly, it's a good idea to make sure that parts of your URLs match to a logical structure in your website (e.g. `/blog/posts/awesome-list-of-seo-tips`). Finally, take the time to build a sitemap and redirect any broken or old URLs to new ones. This will help reduce 404 pages.
|
||||
First and foremost, start at the very beginning, which is your **URL slugs**. For URL slugs the rule is that you want them to be human-readable, with words separated by dashes and with no random letters or digits added. Secondly, it's a good idea to make sure that parts of your URLs match to a **logical structure** in your website (e.g. `/blog/posts/awesome-list-of-seo-tips`). Finally, take the time to build a sitemap and redirect any broken or old URLs to new ones. This will help reduce 404 pages.
|
||||
|
||||
### Use structured data to help Google understand your pages
|
||||
|
||||
Structured data is what Google uses to power its featured snippets, those little cards that appear at the top of certain searches. It's a good idea to set up structured data for your pages, which will help you rank higher in search results and possibly even land a featured snippet every once in a while. Just make sure to find the appropriate structured data type and include it in your page and you should be good to go.
|
||||
**Structured data** is what Google uses to power its **featured snippets**, those little cards that appear at the top of certain searches. It's a good idea to set up structured data for your pages, which will help you rank higher in search results and possibly even land a featured snippet every once in a while. Just make sure to find the appropriate structured data type and include it in your page and you should be good to go.
|
||||
### Set up Google Analytics and Google Search Console
|
||||
|
||||
This is hardly a new tip, but I think it deserves a spot on the list, as both of these tools are extremely important. Google Analytics allows you to track user behavior and gather data that can help you identify problems and opportunities, while Google Search Console is helpful for figuring out what users are searching for before landing on your website.
|
||||
This is hardly a new tip, but I think it deserves a spot on the list, as both of these tools are extremely important. **Google Analytics** allows you to track user behavior and gather data that can help you identify problems and opportunities, while **Google Search Console** is helpful for figuring out what users are searching for before landing on your website.
|
||||
|
||||
### Keep an eye on your markup, performance and accessibility
|
||||
|
||||
Last but not least, something that is probably already on your list, is optimizing your code. This includes everything from writing semantic markup and keeping requests to a minimum to optimizing for all device types, making your website accessible and ensuring fast load times. Keep in mind that websites have many moving parts that evolve and change over time, so it's a good idea to audit your website often with a tool like Lighthouse.
|
||||
Last but not least, something that is probably already on your list, is **optimizing your code**. This includes everything from writing semantic markup and keeping requests to a minimum to optimizing for all device types, making your website accessible and ensuring fast load times. Keep in mind that websites have many moving parts that evolve and change over time, so it's a good idea to audit your website often with a tool like Lighthouse.
|
||||
|
||||
@ -7,17 +7,25 @@ author: chalarangelo
|
||||
cover: blog_images/laptop-view.jpg
|
||||
excerpt: Writing about a topic can often sound boring or worthless. But there are significant benefits to doing it from time to time.
|
||||
firstSeen: 2021-04-12T12:00:00+03:00
|
||||
lastUpdated: 2021-06-12T19:30:41+03:00
|
||||
lastUpdated: 2022-11-05T14:00:07+03:00
|
||||
---
|
||||
|
||||
I was recently tasked with writing a design document at work. What first seemed like a tiresome chore I had to endure turned out to be both enlightening and beneficial both to me and my team. This isn't just a case of making the best of a bad situation, but rather a case of rediscovering the reason behind why writing down your thoughts isn't an exercise in futility. Instead of boring you with a long-winded story about the task, I'll just present the takeaways and hopefully do some storytelling in the process. So why should you write then?
|
||||
|
||||
**You learn more about the topic at hand.** Presenting a topic requires meticulous research in order for you to be able to write down the most interesting and relevant findings. This process will uncover any gaps in your knowledge of said topic, gaps your research will in turn have to cover. I've seen this numerous times in the past while doing research both for this website as well as tasks at work and it always holds true. If nothing else, you will gain a deeper understanding of whatever it is you are presenting, which will be useful sometime in the future. In the case of my task, I understood more about the business logic, how premature optimizations got in the way of stability and why it was so hard to consistently reach the desired state, as I discovered a hidden layer of complexity I didn't know about before.
|
||||
### You learn more about the topic at hand
|
||||
|
||||
**You discover the root of the problem.** Most of the time we assume to know the problem well enough to try and solve it. A lot of the time, I've found out, I know a lot less than I originally thought about the domain, the problem and, more importantly, the root cause of it. Again, presenting the problem to a reader will put you through the process of having to explain it and boil it down to the key details. This more often than not results in a concise description of whatever is at the root of the problem. Working on my design document, I figured out that the root of the problem was essentially a bad set of assumptions that kept piling up and had some mismatches between the backend and frontend code.
|
||||
Presenting a topic requires meticulous **research** in order for you to be able to write down the most interesting and relevant findings. This process will uncover any **gaps in your knowledge** of said topic, gaps your research will in turn have to cover. I've seen this numerous times in the past while doing research both for this website as well as tasks at work and it always holds true. If nothing else, you will gain a **deeper understanding** of whatever it is you are presenting, which will be useful sometime in the future. In the case of my task, I understood more about the business logic, how premature optimizations got in the way of stability and why it was so hard to consistently reach the desired state, as I discovered a hidden layer of complexity I didn't know about before.
|
||||
|
||||
**You understand past decisions.** This one applies to problems you aren't the first one to tackle, but might still be relevant in other situations. As you go you will come across whatever implementation details you didn't know about. If you take a closer look, you'll figure out how those came to be in the first place and why those decisions were made in the past. Understanding the reasons behind past decisions is crucial in figuring out if the original set of needs, restrictions and assumptions is still valid and how to amend them if necessary. As I was already knee-deep in this task, I realized that the decisions we made almost one year prior were valid at the time, decisions I didn't fully understand back then. A couple of them, however, didn't seem to stand the test of time as business needs changed and we'd have to update them accordingly.
|
||||
### You discover the root of the problem
|
||||
|
||||
**You come up with more varied solutions.** Understanding past decisions, covering the gaps in your knowledge and boiling down the problem to a concise description you should already be a lot more informed about the topic you are researching than before. Hopefully at this stage, you will have an itch to try a different solution from the one you originally envisioned or you'll have come up with a solution where there was none. More varied solutions are always better because you can compare tradeoffs and get a better understanding of what's best. In my case, I ended up considering a solution I had mostly dismissed originally. And it was that solution which made the most sense to me in the end, even though it was more radical than the one I was planning to propose originally.
|
||||
Most of the time we assume to know the problem well enough to try and solve it. A lot of the time, I've found out, I know a lot less than I originally thought about the domain, the problem and, more importantly, the root cause of it. Again, **presenting the problem to a reader** will put you through the process of having to **explain it** and boil it down to the key details. This more often than not results in a concise description of whatever is at the **root of the problem**. Working on my design document, I figured out that the root of the problem was essentially a set of bad assumptions that kept piling up and had some mismatches between the backend and frontend code.
|
||||
|
||||
### You understand past decisions
|
||||
|
||||
This one applies to problems you aren't the first one to tackle, but might still be relevant in other situations. As you go you will come across whatever **implementation details** you didn't know about. If you take a closer look, you'll figure out how those came to be in the first place and **why those decisions were made** in the past. Understanding the reasons behind past decisions is crucial in figuring out if the original set of needs, restrictions and assumptions is still valid and how to amend them if necessary. As I was already knee-deep in this task, I realized that the decisions we made almost one year prior were valid at the time, decisions I didn't fully understand back then. A couple of them, however, didn't seem to stand the test of time as business needs changed and we'd have to update them accordingly.
|
||||
|
||||
### You come up with more varied solutions
|
||||
|
||||
Understanding past decisions, covering the gaps in your knowledge and boiling down the problem to a concise description you should already be a lot more informed about the topic you are researching than before. Hopefully at this stage, you will have an itch to try **a different solution** from the one you originally envisioned or you'll have come up with a solution where there was none. More varied solutions are always better because you can **compare tradeoffs** and get a better understanding of what's best. In my case, I ended up considering a solution I had mostly dismissed originally. And it was that solution which made the most sense to me in the end, even though it was more radical than the one I was planning to propose originally.
|
||||
|
||||
As a closing thought, try writing even if nobody is going to read it. Write it for you. It will help you understand, explain, reason about and tackle the task all at once. It's not a waste of time, it's a powerful tool you could leverage from time to time.
|
||||
|
||||
@ -10,7 +10,7 @@ firstSeen: 2020-01-30T12:35:19+02:00
|
||||
lastUpdated: 2021-11-06T20:51:47+03:00
|
||||
---
|
||||
|
||||
I am by no means an expert React engineer, but I have a couple years of experience under my belt. React is a powerful library for building user interfaces, but it's also quite fragile at places. A common bug I have encountered is caused by direct DOM manipulation in combination with React. This is sort of an anti-pattern, as it can break your entire React application under the right circumstances and it's hard to debug.
|
||||
I am by no means an expert React engineer, but I have a couple years of experience under my belt. React is a powerful library for building user interfaces, but it's also quite fragile at places. A common bug I have encountered is caused by **direct DOM manipulation in combination with React**. This is sort of an anti-pattern, as it can break your entire React application under the right circumstances and it's hard to debug.
|
||||
|
||||
Here's [a minimal example](https://codepen.io/chalarangelo/pen/jOEojVJ?editors=0010) of how to reproduce this bug, before we dive into explaining the problem and how to fix it:
|
||||
|
||||
@ -43,7 +43,7 @@ This is a pretty simple React application, with a container, two buttons and a s
|
||||
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
```
|
||||
|
||||
This might still be cryptic, so let me explain what's going on. React uses its own representation of the DOM, called a virtual DOM, in order to figure out what to render. Usually, the virtual DOM will match the current DOM structure and React will process changes in props and state. It will then update the virtual DOM and then batch and send the necessary changes to the real DOM.
|
||||
This might still be cryptic, so let me explain what's going on. React uses its own representation of the DOM, called a **virtual DOM**, in order to figure out what to render. Usually, the virtual DOM will match the current DOM structure and React will process changes in props and state. It will then update the virtual DOM and then batch and send the necessary changes to the real DOM.
|
||||
|
||||
However, in this case React's virtual DOM and the real DOM are different, because of `destroyElement()` removing the `#my-div` element. As a result, when React tries to update the real DOM with the changes from the virtual DOM, the element cannot be removed as it doesn't exist anymore. This results in the above exception being thrown and your application breaking.
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ As you can see, for each value of `n`, `fibonacciNumber` will be called twice, o
|
||||
|
||||
### Calculation memoization
|
||||
|
||||
The solution to this problem, and the first trick that you can use to speed up recursive functions, is to use memoization. We already published [a great blog post on memoization](https://www.30secondsofcode.org/blog/s/javascript-memoization/) a little while back, so be sure to check it out to learn more about the subject. Here's our `fibonacciNumber` function, using memoization:
|
||||
The solution to this problem, and the first trick that you can use to speed up recursive functions, is to use memoization. We already published [a great blog post on memoization](/articles/s/javascript-memoization/) a little while back, so be sure to check it out to learn more about the subject. Here's our `fibonacciNumber` function, using memoization:
|
||||
|
||||
```js
|
||||
const fibonacciCache = new Map();
|
||||
|
||||
@ -13,11 +13,11 @@ lastUpdated: 2021-09-28T19:35:49+03:00
|
||||
|
||||
### Flexbox
|
||||
|
||||
Using flexbox to vertically and horizontally center content is usually the preferred method. All it takes is three lines of code in the container element to set `display: flex` and then center the child element vertically and horizontally using `align-items: center` and `justify-content: center` respectively. You can view the [Flexbox centering snippet](/css/s/flexbox-centering) for the code and examples.
|
||||
Using flexbox to vertically and horizontally center content is usually the **preferred method**. All it takes is three lines of code in the container element to set `display: flex` and then center the child element vertically and horizontally using `align-items: center` and `justify-content: center` respectively. You can view the [Flexbox centering snippet](/css/s/flexbox-centering) for the code and examples.
|
||||
|
||||
### Grid
|
||||
|
||||
Using the grid module is very similar to flexbox and also a common technique, especially if you are already using grid in your layout. The only difference from the previous technique is the `display` which is set to `grid` instead. You can view the [Grid centering snippet](/css/s/grid-centering) for the code and examples.
|
||||
Using the grid module is very similar to flexbox and also a common technique, especially if you are **already using grid in your layout**. The only difference from the previous technique is the `display` which is set to `grid` instead. You can view the [Grid centering snippet](/css/s/grid-centering) for the code and examples.
|
||||
|
||||
### Transform
|
||||
|
||||
@ -25,4 +25,4 @@ Transform centering uses, as the name implies, CSS transforms to center an eleme
|
||||
|
||||
### Table
|
||||
|
||||
Last but not least, table centering is an older technique which you might favor when working with older browsers. It depends on the use of `display: table` in the container element. This allows the child element to use `display: table-cell` in combination with `text-align: center` and `vertical-align: middle` to center itself horizontally and vertically. You can view the [Display table centering snippet](/css/s/display-table-centering) for the code and examples.
|
||||
Last but not least, table centering is an older technique which you might favor when working with **older browsers**. It depends on the use of `display: table` in the container element. This allows the child element to use `display: table-cell` in combination with `text-align: center` and `vertical-align: middle` to center itself horizontally and vertically. You can view the [Display table centering snippet](/css/s/display-table-centering) for the code and examples.
|
||||
|
||||
@ -10,7 +10,7 @@ excerpt: Make sure the footer stays at the bottom of the page, instead of floati
|
||||
firstSeen: 2022-10-30T05:00:00-04:00
|
||||
---
|
||||
|
||||
Preventing the footer from floating up the page is important when trying to create a polished website. Pages with short content can run into this issue, but it's easy to fix with a few lines of CSS. Assuming your HTML looks something like the snippet below, here are two modern ways to ensure the footer is always at the bottom of the page:
|
||||
Preventing the footer from floating up the page is important when trying to create a polished website. Pages with **short content** can run into this issue, but it's easy to fix with a few lines of CSS. Assuming your HTML looks something like the snippet below, here are two modern ways to ensure the footer is always at the bottom of the page:
|
||||
|
||||
```html
|
||||
<body>
|
||||
@ -21,7 +21,7 @@ Preventing the footer from floating up the page is important when trying to crea
|
||||
|
||||
### Using Flexbox
|
||||
|
||||
You can use flexbox to ensure that the footer is always at the bottom of the page. This is done by setting the giving the `body` element `min-height: 100vh`, `display: flex` and `flex-direction: column`. Then, give the `footer` element a `margin-top: auto` to make its margin fill the remaining space between it and its previous sibling. Note that this technique will not stretch the previous sibling, but rather push the footer to the bottom of the page.
|
||||
You can use flexbox to ensure that the footer is always at the bottom of the page. This is done by setting the giving the `body` element `min-height: 100vh`, `display: flex` and `flex-direction: column`. Then, give the `footer` element a `margin-top: auto` to make its margin fill the remaining space between it and its previous sibling. Note that this technique will not stretch the previous sibling, but rather **push the footer to the bottom of the page**.
|
||||
|
||||
|
||||
```css
|
||||
@ -38,7 +38,7 @@ footer {
|
||||
|
||||
### Using Grid
|
||||
|
||||
You can also use grid in a very similar fashion. Simply swap `display: flex` for `display: grid` and `flex-direction: column` for `grid-template-rows: 1fr auto` in the `body` element. No additional attributes are needed for the `footer` element. In this case, the `fr` unit is leveraged to stretch the `main` element to fill the remaining space.
|
||||
You can also use grid in a very similar fashion. Simply swap `display: flex` for `display: grid` and `flex-direction: column` for `grid-template-rows: 1fr auto` in the `body` element. No additional attributes are needed for the `footer` element. In this case, the `fr` unit is leveraged to stretch the `main` element to **fill the remaining space**.
|
||||
|
||||
```css
|
||||
body {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Tip: Perfect nested border radius in CSS"
|
||||
shortTitle: Perfect nested border radius in CSS
|
||||
type: story
|
||||
type: tip
|
||||
tags: css,visual
|
||||
expertise: beginner
|
||||
author: chalarangelo
|
||||
@ -10,7 +10,7 @@ excerpt: Nesting elements with rounded borders can look very wrong if not done c
|
||||
firstSeen: 2022-04-03T05:00:00-04:00
|
||||
---
|
||||
|
||||
Nesting elements with rounded borders can look very wrong if not done correctly. Luckily, there's a simple math trick to make it look right. All you need to do is calculate the border radius of one of the elements and the distance between them. The border radius of the outer element should be equal to the sum of the border radius of the inner element and the distance between the two elements. This can be mathematically expressed as `innerRadius + distance = outerRadius` or more tersely `R1 + D = R2`.
|
||||
Nesting elements with rounded borders can look very wrong if not done correctly. Luckily, there's a simple math trick to make it look right. All you need to do is **calculate the border radius of one of the elements and the distance between them**. The border radius of the outer element should be equal to the sum of the border radius of the inner element and the distance between the two elements. This can be mathematically expressed as `innerRadius + distance = outerRadius` or more tersely `R1 + D = R2`.
|
||||
|
||||

|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ excerpt: A short, opinionated CSS reset to make your websites look great everywh
|
||||
firstSeen: 2022-10-16T05:00:00-04:00
|
||||
---
|
||||
|
||||
Browsers nowadays are much better at presenting HTML in a consistent manner, making CSS resets of the past largely unnecessary. However, default browser styles are not particularly great in most cases, which is why there are tons of CSS resets out there. Drawing inspiration from some of them, here's my opinionated CSS reset, along with an explanation of why I chose to include each rule.
|
||||
Browsers nowadays are much better at presenting HTML in a consistent manner, making CSS resets of the past largely unnecessary. However, **default browser styles are not particularly great** in most cases, which is why there are tons of CSS resets out there. Drawing inspiration from some of them, here's my opinionated CSS reset, along with an explanation of why I chose to include each rule.
|
||||
|
||||
```css
|
||||
html {
|
||||
|
||||
@ -10,9 +10,9 @@ excerpt: You might have heard that `line-height` should be unitless, but do you
|
||||
firstSeen: 2022-11-27T05:00:00-04:00
|
||||
---
|
||||
|
||||
I've often heard that `line-height` should always be unitless. In my earlier coding years, I didn't question it much, but lately I've come to wonder why that is. In my mind `1.5` and `1.5em` should produce the same result, right? Turns out, they don't.
|
||||
I've often heard that `line-height` should always be **unitless**. In my earlier coding years, I didn't question it much, but lately I've come to wonder why that is. In my mind `1.5` and `1.5em` should produce the same result, right? Turns out, they don't.
|
||||
|
||||
There's a subtle difference between the two and it has to do with the fact that `line-height` is an inherited property. A unitless value will be inherited as-is, meaning the actual value will be recalculated for each element, accounting for the `font-size` of the element. However, a `line-height` with any unit will be calculated once and then inherited as a fixed value. This can cause vastly different results, especially if the declaration is in the `body` element or something similar.
|
||||
There's a **subtle difference** between the two and it has to do with the fact that `line-height` is an inherited property. A unitless value will be inherited as-is, meaning the actual value will be recalculated for each element, accounting for the `font-size` of the element. However, a `line-height` with any unit will be calculated once and then inherited as a fixed value. This can cause vastly different results, especially if the declaration is in the `body` element or something similar.
|
||||
|
||||
Speaking of the `body` element, it could be a good idea to define your base `line-height` as a unitless value there to minimize repetition:
|
||||
|
||||
@ -22,4 +22,4 @@ body {
|
||||
}
|
||||
```
|
||||
|
||||
So, is `line-height` with units prohibited and should we always use unitless values? Not necessarily. Factors such as codebase conventions, design systems and personal preference play a role here. For example, maintaining an exact, perfect vertical rhythm with unitless `line-height` values can be a bit tricky. In such cases, using `line-height` with units can be a good idea, but remember to be consistent to avoid headaches.
|
||||
So, is `line-height` with units prohibited and should we always use unitless values? Not necessarily. Factors such as codebase conventions, design systems and personal preference play a role here. For example, maintaining an exact, perfect vertical rhythm with unitless `line-height` values can be a bit tricky. In such cases, using `line-height` with units can be a good idea, but remember to **be consistent** to avoid headaches.
|
||||
|
||||
@ -11,9 +11,9 @@ firstSeen: 2020-03-09T19:39:30+02:00
|
||||
lastUpdated: 2021-09-28T19:52:58+03:00
|
||||
---
|
||||
|
||||
CSS variables (officially called CSS custom properties) behave much like variables in other programming languages. They allow you to define named variables that contain specific values that can be reused within the CSS document. As specified in the custom property notation, CSS variables are prefixed with two dashes (e.g. `--my-color: black`). To access them, you can use the `var()` function (e.g. `color: var(--my-color)`). CSS variables are exceptionally useful for sharing styles between different elements and components. Examples include but are not limited to vertical rhythm, typography variables and color palettes.
|
||||
CSS variables (officially called CSS **custom properties**) behave much like variables in other programming languages. They allow you to define named variables that contain specific values that can be reused within the CSS document. As specified in the custom property notation, CSS variables are prefixed with two dashes (e.g. `--my-color: black`). To access them, you can use the `var()` function (e.g. `color: var(--my-color)`). CSS variables are exceptionally useful for **sharing styles** between different elements and components. Examples include but are not limited to vertical rhythm, typography variables and color palettes.
|
||||
|
||||
One of their most common use-cases is theming and dark mode. CSS variables can be used to create a shared palette across the whole website and easily swap it for a different one. This is often accomplished by applying a class to a common ancestor (e.g. the `<body>` element). This example demonstrates global variables defined in the `:root` element and cascading, as elements inherit values from their parents:
|
||||
One of their most common use-cases is **theming and dark mode**. CSS variables can be used to create a shared palette across the whole website and easily swap it for a different one. This is often accomplished by applying a class to a common ancestor (e.g. the `<body>` element). This example demonstrates global variables defined in the `:root` element and cascading, as elements inherit values from their parents:
|
||||
|
||||
```css
|
||||
/* Global variables are defined in the :root element. */
|
||||
@ -38,7 +38,7 @@ body.dark {
|
||||
}
|
||||
```
|
||||
|
||||
Another useful example is defining shared customized styles for certain variants of an element. This allows the customization of whole trees of components without having to repeat any styles. The following example demonstrates cascading even better than the previous one. It also introduces the idea of sharing styles between different elements:
|
||||
Another useful example is **defining shared customized styles** for certain variants of an element. This allows the customization of whole trees of components without having to repeat any styles. The following example demonstrates cascading even better than the previous one. It also introduces the idea of sharing styles between different elements:
|
||||
|
||||
```css
|
||||
.btn {
|
||||
@ -63,6 +63,6 @@ Another useful example is defining shared customized styles for certain variants
|
||||
|
||||
Finally, keep in mind the following useful tips for working with CSS variables:
|
||||
|
||||
- You can define fallback values, by providing a second argument to the `var()` function (e.g. `var(--text-color, black)` will default to `black` if `--text-color` is not defined).
|
||||
- CSS variables are case sensitive, so mind your capitalization. They can also be inlined in HTML like any other style (e.g. `<div style="--text-color: red">`).
|
||||
- You can define **fallback values**, by providing a second argument to the `var()` function (e.g. `var(--text-color, black)` will default to `black` if `--text-color` is not defined).
|
||||
- CSS variables are **case sensitive**, so mind your capitalization. They can also be inlined in HTML like any other style (e.g. `<div style="--text-color: red">`).
|
||||
- You can nest `var()` calls, using another variable as fallback (e.g. `var(--main-color, var(--other-color))`), pass them to other functions such as `calc()` or even assign one variable to another (e.g. `--text-color: var(--main-color)`).
|
||||
|
||||
@ -9,32 +9,32 @@ excerpt: Many beginners get stuck in tutorial hell without even realizing. Here
|
||||
firstSeen: 2022-05-26T05:00:00-04:00
|
||||
---
|
||||
|
||||
Tutorial hell refers to the stage in your learning journey, where you are following along with tutorials, not truly learning that much. Tutorials are by nature a curated experience. While that's not definitively bad, it's not always the best way to explore the landscape and familiarize with concepts and tools. Neither is it the best way to learn how to think for yourself. So how can you escape it?
|
||||
Tutorial hell refers to the stage in your learning journey, where you are following along with tutorials, not truly learning that much. Tutorials are by nature a curated experience. While that's not definitively bad, it's **not always the best way** to explore the landscape and familiarize with concepts and tools. Neither is it the best way to learn how to think for yourself. So how can you escape it?
|
||||
|
||||
### Debug
|
||||
|
||||
The most common exit ramp comes up when you end up in debugging mode. Oftentimes a tutorial can be outdated or something in the setup can be just a tiny bit different. At this point, you have to figure out how to solve the issue to proceed.
|
||||
|
||||
Entering debugging mode, you realize that you know only a part of the big picture. What you don't know might not be immediately obvious, but with perseverance you will be able to figure it out. At that point, you can start looking for answers to solve the issue.
|
||||
Entering debugging mode, you realize that **you know only a part of the big picture**. What you don't know might not be immediately obvious, but with perseverance you will be able to figure it out. At that point, you can start looking for answers to solve the issue.
|
||||
|
||||
Debugging a problem offers a little bit of exposure to the underlying technologies and concepts. This way you can dip your toe into whatever it is you are working with, without getting overwhelmed.
|
||||
Debugging a problem offers a little bit of **exposure to the underlying technologies and concepts**. This way you can dip your toe into whatever it is you are working with, without getting overwhelmed.
|
||||
|
||||
### Be curious
|
||||
|
||||
Curiosity killed the cat, but not you. Stop every step of the way to ask what it is you are learning. Do you understand the concepts? Do you know what it is you are building? Is this tool your only option? What problem does it solve? Are there others?
|
||||
|
||||
Questions are a valuable tool to help you explore the landscape. You don't necessarily need to answer them all, nor do you need to read tons of documentation or articles about a topic. Simply taking a peek at the big picture and asking questions is enough. You can always come back later and dive deeper.
|
||||
Questions are a valuable tool to help you explore the landscape. You don't necessarily need to answer them all, nor do you need to read tons of documentation or articles about a topic. Simply **taking a peek at the big picture** and **asking questions** is enough. You can always come back later and dive deeper.
|
||||
|
||||
### Experiment
|
||||
|
||||
The third and best way to escape tutorial hell is to experiment. After you finish a couple of tutorials, put your skills to the test. Try to build something from scratch, looking up anything you need to, as you go. It won't be as easy or as fast as the tutorial was, but you'll learn a lot more.
|
||||
The third and best way to escape tutorial hell is to experiment. After you finish a couple of tutorials, **put your skills to the test**. Try to build something from scratch, looking up anything you need to, as you go. It won't be as easy or as fast as the tutorial was, but you'll learn a lot more.
|
||||
|
||||
After you build a project, take another look. Figure out what you could have done differently or what parts of it you don't understand well enough. Refine it, polish up parts of it, add new features. The more time you put into it, the better you will become.
|
||||
After you build a project, **take another look**. Figure out what you could have done differently or what parts of it you don't understand well enough. Refine it, polish up parts of it, add new features. The more time you put into it, the better you will become.
|
||||
|
||||
Then do it a few more times and experiment with a few more ideas. As soon as you start working on your own projects and get into a rhythm, it's a lot easier to learn new things. Some slopes will be steeper and others will be more gentle. But you will feel much more confident in your skills than blindly following tutorials.
|
||||
Then do it a few more times and experiment with a few more ideas. As soon as you start working on your own projects and get into a rhythm, it's a lot easier to learn new things. Some slopes will be steeper and others will be more gentle. But you will feel much **more confident in your skills** than blindly following tutorials.
|
||||
|
||||
### Learn your own way
|
||||
|
||||
As a closing suggestion, I want to urge you to find your own learning style. Tutorials might be right for some and I don't suggest you dismiss them entirely. After all, we all have to start somewhere. But if they don't work for you, mix it up a bit. Try reading the documentation, solving coding exercises or tinkering with an existing project. Everyone is different and there's no one size fits all when it comes to learning.
|
||||
As a closing suggestion, I want to urge you to **find your own learning style**. Tutorials might be right for some and I don't suggest you dismiss them entirely. After all, we all have to start somewhere. But if they don't work for you, **mix it up a bit**. Try reading the documentation, solving coding exercises or tinkering with an existing project. Everyone is different and there's no one size fits all when it comes to learning.
|
||||
|
||||
For some, much like myself, teaching is the best learning experience. After all, if you can't explain something simply, you probably don't understand it well enough. Explaining, then, can lead you to researching, debugging, experimenting and being curious all at once. And teaching, much like learning, can be done in many many different ways.
|
||||
|
||||
@ -16,10 +16,10 @@ Merging a branch is one of the most common operations when working with Git. Dep
|
||||
|
||||
### Fast-forward merge
|
||||
|
||||
As stated above, Git's default is to use fast-forward merge. It will take the commits from the branch being merged and place them at the tip of the branch you're merging into. This creates a linear history, which is also the main advantage of using fast-forward merge. If you want to emulate fast-forward merge on GitHub, you can use the "Rebase and merge" option.
|
||||
As stated above, Git's default is to use fast-forward merge. It will take the commits from the branch being merged and place them at the tip of the branch you're merging into. This creates a **linear history**, which is also the main advantage of using fast-forward merge. If you want to emulate fast-forward merge on GitHub, you can use the "Rebase and merge" option.
|
||||
|
||||
### Non fast-forward merge
|
||||
|
||||
GitHub, on the other hand, uses non fast-forward merge by default. It will create a merge commit at the tip of the branch you're merging into, optionally referencing the branch being merged in the commit message. This has the advantage of keeping track of branches more explicitly than fast-forward merge. If you want to get the same behavior in a Git terminal, you can use the `--no-ff` flag.
|
||||
GitHub, on the other hand, uses non fast-forward merge by default. It will create a merge commit at the tip of the branch you're merging into, optionally referencing the branch being merged in the commit message. This has the advantage of **keeping track of branches** more explicitly than fast-forward merge. If you want to get the same behavior in a Git terminal, you can use the `--no-ff` flag.
|
||||
|
||||
As a side note, you can configure the default Git merge behavior, using `git config`. To learn how to do so, you can take a look at the [relevant snippet](/git/s/disable-fast-forward).
|
||||
|
||||
@ -25,7 +25,7 @@ obj.c = 3;
|
||||
|
||||
### Square bracket notation
|
||||
|
||||
Similar to dot notation, square bracket notation comes in handy when dealing with dynamic keys, but can also work with static keys. Apart from that, it's exactly the same as dot notation both in functionality and performance.
|
||||
Similar to dot notation, square bracket notation comes in handy when dealing with **dynamic keys**, but can also work with static keys. Apart from that, it's exactly the same as dot notation both in functionality and performance.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
@ -37,7 +37,7 @@ obj['c'] = 3;
|
||||
|
||||
### Object.assign()
|
||||
|
||||
`Object.assign()` is slightly different than the previous two options. It can be used to add multiple properties to an object at once and it can also shallow merge two or more objects. It is not as performant, however, so it should only be used when necessary.
|
||||
`Object.assign()` is slightly different than the previous two options. It can be used to add multiple properties to an object at once and it can also **shallow merge** two or more objects. It is not as performant, however, so it should only be used when necessary.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
@ -47,7 +47,7 @@ Object.assign(obj, { b: 2 }, { c: 3 });
|
||||
|
||||
### Object.defineProperty()
|
||||
|
||||
Another, less-common, way to add a key-value pair to an object is to use `Object.defineProperty()`. This is the lest performant way to add a key-value pair to an object, but it allows the new property to be precisely defined. This function accepts either a data or accessor descriptor as its second argument, allowing the behavior of the new property to be customized as desired. Bear in mind that you can add multiple properties at once, using `Object.defineProperties()`.
|
||||
Another, less-common, way to add a key-value pair to an object is to use `Object.defineProperty()`. This is the lest performant way to add a key-value pair to an object, but it allows the new property to be **precisely defined**. This function accepts either a data or accessor descriptor as its second argument, allowing the behavior of the new property to be customized as desired. Bear in mind that you can add multiple properties at once, using `Object.defineProperties()`.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
@ -68,7 +68,7 @@ Object.defineProperty(obj, 'c', {
|
||||
|
||||
### Object spread operator
|
||||
|
||||
Last but not least, there's the object spread operator (`...`). Contrary to previous methods, this one doesn't mutate the original object, but instead returns a new object with the added properties. As expected, the performance of this approach is significantly worse than previous ones, due to the need to create a new object.
|
||||
Last but not least, there's the object spread operator (`...`). Contrary to previous methods, this one **doesn't mutate the original object**, but instead returns a new object with the added properties. As expected, the performance of this approach is significantly worse than previous ones, due to the need to create a new object.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
|
||||
@ -12,7 +12,7 @@ firstSeen: 2021-11-21T05:00:00-04:00
|
||||
|
||||
### Object-oriented programming
|
||||
|
||||
Both classical and prototypal inheritance are object-oriented programming paradigms. Objects in object-oriented programming are abstractions that encapsulate the properties of an entity. This is known as abstraction.
|
||||
Both classical and prototypal inheritance are **object-oriented programming paradigms**. Objects in object-oriented programming are abstractions that encapsulate the properties of an entity. This is known as abstraction.
|
||||
|
||||
When dealing with multiple levels of abstraction, each level is more general or more specific. The more general abstraction of a more specific abstraction is called a generalization.
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ firstSeen: 2021-02-01T11:00:00+02:00
|
||||
lastUpdated: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
The destructuring assignment syntax, first introduced in JavaScript ES6, allows the unpacking of values from arrays and objects into distinct variables. While it might seem intimidating at first, it's actually quite easy to learn and use. Let's break it down into easier to understand cases.
|
||||
The destructuring assignment syntax, first introduced in JavaScript ES6, allows the **unpacking of values** from arrays and objects into distinct variables. While it might seem intimidating at first, it's actually quite easy to learn and use. Let's break it down into easier to understand cases.
|
||||
|
||||
### Array destructuring
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ I strongly recommend you read the [previous article on closures](/articles/s/jav
|
||||
|
||||
### Hidden state
|
||||
|
||||
The main argument against closures is that of hidden state. Hidden state refers to obscuring the state of an object or, in this case, a function. The argument is that internal mutable state can create unpredictable behavior and unexpected results. Due to this, it's often said that hidden state is the root of all evil when it comes to programming.
|
||||
The main argument against closures is that of hidden state. Hidden state refers to obscuring the state of an object or, in this case, a function. The argument is that **internal mutable state** can create unpredictable behavior and unexpected results. Due to this, it's often said that hidden state is the root of all evil when it comes to programming.
|
||||
|
||||
While the argument in itself has some merit, I don't much like the generalization. There are perfectly valid cases where hidden state is expected, even practically a necessity. However, it's true that hidden state can create bugs and unmaintainable code.
|
||||
|
||||
@ -43,13 +43,13 @@ counter.reset(); // 5
|
||||
|
||||
In this scenario, the `initCounter` function returns an object that contains hidden mutable state in the form of the `value` variable. Obviously, this is a very simple example, but `counter.get()` or `counter.increment()` in isolation would be considered non-deterministic expressions. There is no way to know the result of a method call like that without analyzing the surrounding code.
|
||||
|
||||
While this is not uncommon, it can get more complicated when shared state comes into play or many pieces of code are interacting with one another. The common remedy to this issue is to use functional programming and refactor the hidden mutable state into an argument or a shared global variable.
|
||||
While this is not uncommon, it can get more complicated when shared state comes into play or many pieces of code are interacting with one another. The common remedy to this issue is to use **functional programming** and refactor the hidden mutable state into an argument or a shared global variable.
|
||||
|
||||
### Access to context
|
||||
|
||||
Not all closures are created equal. In fact, there are perfectly valid use-cases of closures that can make life a lot easier. For example, accessing shared constants should be considered pretty safe. After all, if you want truly pure functions you shouldn't even access globals and web APIs. This would be pretty impractical, as you would have to pass each global and API as an argument to the function.
|
||||
Not all closures are created equal. In fact, there are perfectly valid use-cases of closures that can make life a lot easier. For example, **accessing shared constants** should be considered pretty safe. After all, if you want truly pure functions you shouldn't even access globals and web APIs. This would be pretty impractical, as you would have to pass each global and API as an argument to the function.
|
||||
|
||||
Although reasonably safe, it's important to ensure that constants are initialized before being used and explicitly throw an error, if not. Additionally, adequate documentation of such closures will minimize friction and make sure other developers understand what's going on. Finally, providing an escape hatch, usually in the form of default arguments that can be overridden, should be considered if possible.
|
||||
Although reasonably safe, it's important to ensure that constants are initialized before being used and explicitly throw an error, if not. Additionally, adequate documentation of such closures will minimize friction and make sure other developers understand what's going on. Finally, providing an escape hatch, usually in the form of **default arguments** that can be overridden, should be considered if possible.
|
||||
|
||||
Here's an example of a simple random number generator, following these rules:
|
||||
|
||||
|
||||
@ -11,11 +11,11 @@ firstSeen: 2020-09-24T12:54:08+03:00
|
||||
lastUpdated: 2021-11-07T16:34:37+03:00
|
||||
---
|
||||
|
||||
Higher-order functions are functions that operate on other functions, either by taking them as arguments or by returning them as their results. This allows us to create an abstraction layer over actions, not just values.
|
||||
Higher-order functions are **functions that operate on other functions**, either by accepting them as arguments or by returning them as their results. This allows us to create an abstraction layer over actions, not just values.
|
||||
|
||||
The reason we can write higher-order functions in JavaScript is due to the fact that functions are values. This means they can be assigned to variables and passed as values. You might also often hear the term _callback_ when referring to a function that is passed as an argument. This is due to it being called by the higher-order function. Callbacks are particularly common in JavaScript, with event handling, asynchronous code and array operations relying heavily on them.
|
||||
|
||||
The main advantages of this technique are abstraction, composition, code reusability and readability. Most of the 30 seconds of code snippets are built with higher-order functions in mind. They are small, easily digestible functions that are highly reusable and can be composed to create more complex logic.
|
||||
The main advantages of this technique are abstraction, composition, code reusability and readability. Most of the 30 seconds of code snippets are built with higher-order functions in mind. They are small, easily digestible functions that are **highly reusable** and **can be composed** to create more complex logic.
|
||||
|
||||
That being said, we can take a look at an example, utilizing some very simple functions:
|
||||
|
||||
@ -29,4 +29,4 @@ const evenValues = data.filter(isEven); // [2, 4, 6]
|
||||
const evenSum = data.filter(isEven).reduce(add); // 12
|
||||
```
|
||||
|
||||
In this example, we define two simple functions that we then use as callbacks in `Array.prototype.reduce()` and `Array.prototype.filter()` to get the result we want. Both of these functions are higher-order functions. This allows us to create an abstraction layer for any action we might want to perform without having to rewrite how the filtering or reduction algorithm is to be applied every single time.
|
||||
In this example, we define two simple functions that we then use as callbacks in `Array.prototype.reduce()` and `Array.prototype.filter()` to get the result we want. Both of these functions are higher-order functions. This allows us to create an **abstraction layer for any action** we might want to perform without having to rewrite how the filtering or reduction algorithm is to be applied every single time.
|
||||
|
||||
@ -11,7 +11,7 @@ firstSeen: 2020-02-27T16:23:25+02:00
|
||||
lastUpdated: 2021-11-07T16:34:37+03:00
|
||||
---
|
||||
|
||||
Memoization is a commonly used technique that can help speed up your code significantly. This technique relies on a cache to store results for previously completed units of work. The purpose of the cache is to avoid performing the same work more than once, speeding up subsequent calls of time-consuming functions. Based on this definition, we can easily extract some criteria that can help us decide when to use memoization in our code:
|
||||
Memoization is a commonly used technique that can help speed up your code significantly. This technique relies on a **cache** to store results for previously completed units of work. The purpose of the cache is to **avoid performing the same work more than once**, speeding up subsequent calls of time-consuming functions. Based on this definition, we can easily extract some criteria that can help us decide when to use memoization in our code:
|
||||
|
||||
- Memoization is useful mainly in speeding up slow-performing, costly or time-consuming function calls
|
||||
- Memoization speeds up subsequent calls, so it's best used when you anticipate multiple calls of the same function under the same circumstances
|
||||
|
||||
@ -15,7 +15,7 @@ The other day, I stumbled upon some code where I needed to handle an object as a
|
||||
|
||||
So I thought I could create some kind of wrapper that would take an object and define some array-like behavior for it. I was mainly in need of `Array.prototype.map()`, `Array.prototype.find()`, `Array.prototype.includes()` and `Array.prototype.length`. All of this functionality was pretty straightforward to create using `Object` methods. The only tricky part, so to speak, was getting the object to behave as an iterable, which required using the `Symbol.iterator` and a generator function.
|
||||
|
||||
Injecting the new functionality into an object could be as simple as adding the methods to it. The downside of this approach is that they would be part of the actual object, which can be problematic. It also doesn't help that this is not very reusable if we want to apply this over a handful of objects.
|
||||
Injecting the new functionality into an object could be as simple as adding the methods to it. The downside of this approach is that they would be part of the actual object, which can be problematic. It also doesn't help that this is not very reusable if we want to apply this over a handful of objects.
|
||||
|
||||
Enter the [Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), one of the lesser known tools in a JavaScript developer's tool belt, yet a very powerful one. It's used to intercept certain operations for an object, such as property lookup, assignment etc. In this scenario, it can neatly wrap the required functionality into a function that creates a proxy around the object.
|
||||
|
||||
|
||||
@ -10,9 +10,9 @@ excerpt: How JavaScript handles passing data is a source of confusion and bugs f
|
||||
firstSeen: 2021-12-05T05:00:00-04:00
|
||||
---
|
||||
|
||||
JavaScript is always pass-by-value. This means everything in JavaScript is a value type and function arguments are always passed by value. That being said, object types are a bit more confusing.
|
||||
JavaScript is always **pass-by-value**. This means everything in JavaScript is a value type and function arguments are always passed by value. That being said, object types are a bit more confusing.
|
||||
|
||||
The confusion lies in the fact that object types are reference types which are passed by value. As weird as this sounds, a reference to an object is passed to a function by value. The subtle difference here lies in the fact that an object reference passed by value is not the same as passing an object by reference.
|
||||
The confusion lies in the fact that **object types are reference types** which are passed by value. As weird as this sounds, a reference to an object is passed to a function by value. The subtle difference here lies in the fact that an object reference passed by value is not the same as passing an object by reference.
|
||||
|
||||
Simply put, changes to the object inside the function will affect the original object, as they both refer to the same object. However, reassigning the value of the variable holding the object originally will not affect the object referenced by the function. Let me demonstrate this with an example:
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ excerpt: Recursion is a very important programming concept all developers should
|
||||
firstSeen: 2022-01-23T05:00:00-04:00
|
||||
---
|
||||
|
||||
Recursion is the repeated application of a process. In JavaScript, recursion involves functions that call themselves repeatedly until they reach a base case. The base case breaks out of the recursion loop, thus allowing previous calls to the function to return a result. If no such case exists, the function will call itself indefinitely resulting in a stack overflow.
|
||||
Recursion is the **repeated application of a process**. In JavaScript, recursion involves functions that call themselves repeatedly until they reach a base case. The base case breaks out of the recursion loop, thus allowing previous calls to the function to return a result. If no such case exists, the function will call itself indefinitely resulting in a stack overflow.
|
||||
|
||||
Recursion is used to solve problems where the solution depends on solutions to smaller instances of the same problem. A commonly-used example of a problem that can be solved recursively is the Fibonacci sequence:
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ firstSeen: 2020-04-14T16:19:56+03:00
|
||||
lastUpdated: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript's primitive data types, such as numbers, strings, null, undefined and booleans are immutable, meaning their value cannot change once created. However, objects and arrays are mutable, allowing their value to be altered after creation. What this means in practice is that primitives are passed by value, whereas objects and arrays are passed by reference. Consider the following example:
|
||||
JavaScript's primitive data types, such as numbers, strings, null, undefined and booleans are immutable, meaning their value cannot change once created. However, **objects and arrays are mutable**, allowing their value to be altered after creation. What this means in practice is that primitives are passed by value, whereas objects and arrays are **passed by reference**. Consider the following example:
|
||||
|
||||
```js
|
||||
let str = 'Hello';
|
||||
@ -39,7 +39,7 @@ clone.b = 4;
|
||||
// obj = { a: 1, b: 2}, otherClone = { a: 1, b: 6 }
|
||||
```
|
||||
|
||||
Both of these solutions showcase an example of shallow cloning, as they will work for the outer (shallow) object, but fail if we have nested (deep) objects which will ultimately be passed by reference. As usual, there are a few approaches to this problem, the simpler of which is using `JSON.stringify()` and `JSON.parse()` to deal with the situation:
|
||||
Both of these solutions showcase an example of **shallow cloning**, as they will work for the outer (shallow) object, but fail if we have nested (deep) objects which will ultimately be passed by reference. As usual, there are a few approaches to this problem, the simpler of which is using `JSON.stringify()` and `JSON.parse()` to deal with the situation:
|
||||
|
||||
```js
|
||||
let obj = { a: 1, b: { c: 2 } };
|
||||
|
||||
@ -11,7 +11,7 @@ firstSeen: 2020-02-25T16:02:03+02:00
|
||||
lastUpdated: 2021-09-28T20:11:55+03:00
|
||||
---
|
||||
|
||||
A singleton is an object-oriented software design pattern which ensures a given class is only ever instantiated once. It can be useful in many different situations, such as creating global objects shared across an application. While JavaScript supports object-oriented programming, it doesn't provide many simple options to implement this pattern.
|
||||
A singleton is an **object-oriented software design pattern** which ensures a given class is only ever instantiated once. It can be useful in many different situations, such as creating global objects shared across an application. While JavaScript supports object-oriented programming, it doesn't provide many simple options to implement this pattern.
|
||||
|
||||
The most flexible, albeit somewhat advanced, approach involves using the [Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). The Proxy object is used to define so-called traps. Traps are methods that allow the definition of custom behavior for certain operations such as property lookup, assignment etc. The singleton pattern dictates that the given class can only have one instance. This means that the most useful trap is `handler.construct()`, the trap for the `new` operator.
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ excerpt: When it comes to immutability, many developers have trouble wrapping th
|
||||
firstSeen: 2021-10-10T05:00:00-04:00
|
||||
---
|
||||
|
||||
String specifications among programming languages vary, however most languages treat them as reference types. But strings in JavaScript are different. They are immutable primitives. This means that the characters within them may not be changed and that any operations on strings actually create new strings.
|
||||
String specifications among programming languages vary, however most languages treat them as reference types. But strings in JavaScript are different. They are **immutable primitives**. This means that the characters within them may not be changed and that any operations on strings actually create new strings.
|
||||
|
||||
```js
|
||||
const x = 'type';
|
||||
|
||||
@ -23,7 +23,7 @@ a = b;
|
||||
b = tmp;
|
||||
```
|
||||
|
||||
While this approach still works, there are more elegant and less verbose options available to us nowadays. For example, JavaScript ES6 introduced destructuring assignments, allowing individual array items to be assigned to variables in a single statement. Here's what that looks like:
|
||||
While this approach still works, there are more elegant and less verbose options available to us nowadays. For example, JavaScript ES6 introduced **destructuring assignments**, allowing individual array items to be assigned to variables in a single statement. Here's what that looks like:
|
||||
|
||||
```js
|
||||
const [x, y] = [1, 2];
|
||||
|
||||
@ -10,7 +10,7 @@ excerpt: JavaScript uses type coercion in Boolean contexts, resulting in truthy
|
||||
firstSeen: 2021-09-12T05:00:00-04:00
|
||||
---
|
||||
|
||||
JavaScript uses type coercion (implicit conversion of values from one data type to another) in Boolean contexts, such as conditionals. This means that values are considered either truthy (evaluate to `true`) or falsy (evaluate to `false`) depending on how they are evaluated in a Boolean context.
|
||||
JavaScript uses **type coercion** (implicit conversion of values from one data type to another) in Boolean contexts, such as conditionals. This means that values are considered either truthy (evaluate to `true`) or falsy (evaluate to `false`) depending on how they are evaluated in a Boolean context.
|
||||
|
||||
There are 6 values that are considered **falsy** in JavaScript:
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ firstSeen: 2022-11-02T05:00:00-04:00
|
||||
|
||||
JavaScript is one of the most flexible languages out there, but sometimes this comes with performance costs attached. One such example is the use of dynamically added properties to objects. Oddly enough, this performance impact comes from JavaScript engines optimizing for static typing.
|
||||
|
||||
The V8 engine, which powers Chrome and Node.js, uses shapes and transition chains to optimize object property access. When two objects have the same properties, they are considered to have the same shape. Adding new properties to an object, creates a transition chain to add the new property. Without getting into further detail, it's obvious that a simple shape is faster to access than a transition chain.
|
||||
The V8 engine, which powers Chrome and Node.js, uses **shapes** and **transition chains** to optimize object property access. When two objects have the same properties, they are considered to have the same shape. Adding new properties to an object, creates a transition chain to add the new property. Without getting into further detail, it's obvious that a simple shape is faster to access than a transition chain.
|
||||
|
||||
```js
|
||||
// Create an object with a single property
|
||||
@ -25,7 +25,7 @@ console.log(obj.a);
|
||||
|
||||
Circling back to dynamically added properties, the engine cannot know ahead of time what properties will be added to the object. Thus, it ends up creating a transition chain for each new property that is added. This is not a big deal for a few properties, but it can become a problem when adding a lot of properties to an object.
|
||||
|
||||
Luckily, this is easy to fix. The easiest solution is to define all possible properties of an object ahead of time and give them an empty value (i.e. `null` or `undefined`). This way, the engine can create a shape for the object and optimize property access. This is not always possible, but it's a good practice to follow.
|
||||
Luckily, this is easy to fix. The easiest solution is to **define all possible properties of an object ahead of time** and give them an empty value (i.e. `null` or `undefined`). This way, the engine can create a shape for the object and optimize property access. This is not always possible, but it's a good practice to follow.
|
||||
|
||||
```js
|
||||
// Create an object with all possible properties
|
||||
|
||||
@ -10,17 +10,17 @@ excerpt: Email address validation can be much trickier than it sounds. Here's wh
|
||||
firstSeen: 2022-10-05T05:00:00-04:00
|
||||
---
|
||||
|
||||
One of the most frequent code snippet requests I get is about a function that can validate email addresses. While it sounds easy, I’ve been putting off writing about this topic for a while. The reason is that there are no great solutions to this sort of problem.
|
||||
One of the most frequent code snippet requests I get is about a function that can validate email addresses. While it sounds easy, I’ve been putting off writing about this topic for a while. The reason is that there are **no great solutions to this sort of problem**.
|
||||
|
||||
Theoretically, email addresses can be validated using a regular expression. After all, there are a couple of simple rules, such as the presence of the at (`@`) symbol and an appropriate domain, right? Yes, but there are tons of valid email address that don’t look exactly like that. In fact, there’s a whole standard, [RFC 2822](https://www.rfc-editor.org/rfc/rfc2822#section-3.4.1), that defines what email addresses can look like. Then, there’s the issue of what different mail servers will allow. Gmail, for example, will not allow underscores (`_`) or more than one period (`.`) in a row.
|
||||
|
||||
Additionally, even if you could validate an email address, there’s no way of knowing if this address is in fact currently in use. The only way to do so, is to send an email and check the response. This is why most websites and apps nowadays send you a confirmation email in the first place.
|
||||
Additionally, even if you could validate an email address, there’s **no way of knowing if this address is in fact currently in use**. The only way to do so, is to send an email and check the response. This is why most websites and apps nowadays send you a confirmation email in the first place.
|
||||
|
||||
Finally, even if you used a regular expression that is compliant with RFC 2822, it wouldn’t be without issues. Understading how it works or figuring out if it works correctly for each and every case would be pretty difficult. More importantly, though, it could be prone to regular expression denial of service (ReDoS) attacks, if implemented incorrectly.
|
||||
Finally, even if you used a regular expression that is compliant with RFC 2822, it wouldn’t be without issues. Understading how it works or figuring out if it works correctly for each and every case would be pretty difficult. More importantly, though, it could be prone to **regular expression denial of service (ReDoS) attacks**, if implemented incorrectly.
|
||||
|
||||
By now, you should be starting to figure out why I’ve been hesitant to showcase a solution to the problem of email validation. While solutions do exist, the implications of each one must be considered carefully.
|
||||
|
||||
My suggestion would be to check for basic structural elements on the frontend, then send a confirmation email from your server to check that the email address is in use. It’s easy to implement and gets the job done. Going back to the original idea, here’s a simple function that checks for the most common syntax errors.
|
||||
My suggestion would be to **check for basic structural elements** on the frontend, then **send a confirmation email** from your server to check that the email address is in use. It’s easy to implement and gets the job done. Going back to the original idea, here’s a simple function that checks for the most common syntax errors.
|
||||
|
||||
```js
|
||||
const isEmailValid = address => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(address);
|
||||
|
||||
@ -12,7 +12,7 @@ firstSeen: 2022-11-13T05:00:00-04:00
|
||||
|
||||
Counting the frequency of unique values in an array is reasonably easy, as demonstrated in the [frequencies snippet](/js/s/frequencies). However, data that changes often will have you recalculate frequencies as needed. This can become tedious and inefficient, especially if you only need to keep track of the frequencies and have no need for the original array.
|
||||
|
||||
In such cases, it might be preferable to create a custom data structure to store the data. This data structure will be able to keep track of the frequencies of the values it contains and update them as needed. Here's how you can implement such a data structure:
|
||||
In such cases, it might be preferable to create a **custom data structure** to store the data. This data structure will be able to **keep track of the frequencies of the values** it contains and update them as needed. Here's how you can implement such a data structure:
|
||||
|
||||
```js
|
||||
class FrequencyMap extends Map {
|
||||
|
||||
@ -12,7 +12,7 @@ firstSeen: 2022-10-02T05:00:00-04:00
|
||||
|
||||
`Array.prototype.slice()` provides an easy way to access elements from the end of an array, using a negative `start` value. While this sounds convenient, the resulting value is an array, so it's necessary to use an index to get an individual element.
|
||||
|
||||
While this is usually not too bad, it's interesting to explore other options to understand the language better. In this case, we can use a `Proxy` object to allow accessing data in an array using negative indexes. To do so, an appropriate handler needs to be defined for the `get` trap.
|
||||
This is usually not too bad, but it's interesting to explore other options to understand the language better. In this case, we can use a `Proxy` object to allow accessing data in an array using negative indexes. To do so, an appropriate handler needs to be defined for the `get` trap.
|
||||
|
||||
The trap's second argument corresponds to the passed index, however it's a string, so it must first be converted to a number using `Number()`. Then, `Array.prototype.length` can be used to calculate the position of the actual element. Finally, `Reflect.get()` can be used to get the value at the specific index, but expects its second argument to be a string.
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ excerpt: Mutable default arguments can trip up Python beginners and veterans ali
|
||||
firstSeen: 2022-02-27T05:00:00-04:00
|
||||
---
|
||||
|
||||
Default arguments in Python are evaluated only once. The evaluation happens when the function is defined, instead of every time the function is called. This can inadvertently create hidden shared state, if you use a mutable default argument and mutate it at some point. This means that the mutated argument is now the default for all future calls to the function as well.
|
||||
Default arguments in Python are evaluated only once. The evaluation happens when the function is defined, instead of every time the function is called. This can inadvertently create **hidden shared state**, if you use a mutable default argument and mutate it at some point. This means that the mutated argument is now the default for all future calls to the function as well.
|
||||
|
||||
Take the following code as an example. Every call to the function shares the same list. So, the second time it's called, the function doesn't start out with an empty list. Instead, the default argument is the list containing the value from the previous call.
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ firstSeen: 2022-08-05T05:00:00-04:00
|
||||
|
||||
When working with Python strings, a pretty common question is how does one check if a string is empty. There's a straightforward answer to this that takes advantage of the truth value of strings.
|
||||
|
||||
In Python, any object can be tested for truth value, including strings. In this context, strings are considered truthy if they are non-empty, meaning they contain at least one character. Thus, simply using the `not` operator, you can check if a string is empty.
|
||||
In Python, any object can be tested for truth value, including strings. In this context, **strings are considered truthy if they are non-empty**, meaning they contain at least one character. Thus, simply using the `not` operator, you can check if a string is empty.
|
||||
|
||||
```py
|
||||
empty_string = ''
|
||||
|
||||
@ -29,7 +29,7 @@ print(b) # 11
|
||||
|
||||
### Without a temporary variable (Tuple swap)
|
||||
|
||||
Another way to swap the values of two variables, without using a temporary variable, is to use tuple packing and sequence unpacking. Tuples can be constructed in a number of ways, one of which is by separating tuple items using commas. Moreover, Python evaluates the right-hand side of an assignment before its left-hand side. So, by separating the variables with commas on the right side of the statement the variables are packed into a tuple and unpacked by placing the same number of comma-separated target variables on the left side.
|
||||
Another way to swap the values of two variables, without using a temporary variable, is to use **tuple packing** and **sequence unpacking**. Tuples can be constructed in a number of ways, one of which is by separating tuple items using commas. Moreover, Python evaluates the right-hand side of an assignment before its left-hand side. So, by separating the variables with commas on the right side of the statement the variables are packed into a tuple and unpacked by placing the same number of comma-separated target variables on the left side.
|
||||
|
||||
This method of variable swapping and permutation can be used for more than two variables as long as the same number of variables are on both sides of the statement.
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ lastUpdated: 2021-11-07T16:34:37+03:00
|
||||
|
||||
When developing React components, you often need to conditionally apply a `className` attribute to one or more elements. Sometimes, you will have two or more possible values depending on a condition. But there are also times that you might apply a `className` based on a condition or leave it completely empty otherwise.
|
||||
|
||||
There is a correct way to handle a conditional empty className and an incorrect one. Surprisingly, the incorrect way is pretty common and examples of it can be found all around the web. Consider the following code:
|
||||
There is a correct way to handle a conditional empty `className` and an incorrect one. Surprisingly, the incorrect way is pretty common and examples of it can be found all around the web. Consider the following code:
|
||||
|
||||
```jsx
|
||||
const MyComponent = ({ enabled }) => {
|
||||
|
||||
@ -11,9 +11,9 @@ firstSeen: 2020-11-27T13:25:30+02:00
|
||||
lastUpdated: 2021-09-28T19:40:01+03:00
|
||||
---
|
||||
|
||||
The rise of dark mode in recent years has made many website favicons feel awkward or even impossible to see in some cases. Provided you have the appropriate assets, it's relatively easy to create a responsive favicon that can adapt to the user's color scheme preferences.
|
||||
The rise of dark mode in recent years has made many website favicons feel awkward or even impossible to see in some cases. Provided you have the appropriate assets, it's relatively easy to create a responsive favicon that can **adapt to the user's color scheme preferences**.
|
||||
|
||||
In order to create a responsive favicon, you need an SVG icon with as few colors as possible and two color palettes, one for light mode and one for dark mode. Usual rules about icon clarity and complexity apply, so make sure your icon meets all the necessary criteria to be visually distinguishable in any scenario. In our example, we will be using a monochrome icon from the fantastic [Feather icon set](https://feathericons.com/).
|
||||
In order to create a responsive favicon, you need an **SVG icon** with as few colors as possible and two color palettes, one for light mode and one for dark mode. Usual rules about icon clarity and complexity apply, so make sure your icon meets all the necessary criteria to be visually distinguishable in any scenario. In our example, we will be using a monochrome icon from the fantastic [Feather icon set](https://feathericons.com/).
|
||||
|
||||
Leveraging embedded styles in SVG images and the `prefers-color-scheme` media query, we can create an appropriate `<g>` element to group all the elements of the icon. Then, using the `id` of the group, we can apply the color palette for each design. Here's what our final SVG asset looks like:
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ firstSeen: 2022-03-13T05:00:00-04:00
|
||||
|
||||
Testing React components can get pretty complicated, especially when dealing with portals. While they seem intimidating, what they are in essence is a way to render a component in a different place in the DOM. Apart from that, when writing tests, one should avoid testing framework internals. This obviously applies to React internals as well.
|
||||
|
||||
Putting these two points together, all we really care about when testing React portals is if the portalized output is correct. Based on that, mocking portals shouldn't be all that hard. We just need to mock `ReactDOM.createPortal()` to render the input element in place. Here's what that looks like in Jest:
|
||||
Putting these two points together, all we really care about when testing React portals is if the **portalized output** is correct. Based on that, mocking portals shouldn't be all that hard. We just need to mock `ReactDOM.createPortal()` to render the input element in place. Here's what that looks like in Jest:
|
||||
|
||||
```jsx
|
||||
describe('MyComponent', () => {
|
||||
|
||||
@ -12,13 +12,13 @@ lastUpdated: 2021-11-07T16:34:37+03:00
|
||||
|
||||
Building a typographic scale might seem hard. Yet it's not all that difficult, as long as you learn some basic techniques and principles.
|
||||
|
||||
The first steps are to pick a font family, a starting value for the font size and a ratio which will serve as the scaling factor. Common values for these variables are the Roboto font family, a great choice due to its many different font weights, a starting font size of `18px` and a ratio of `1.618`, which is the golden ratio.
|
||||
The first steps are to pick a **font family**, a starting value for the **font size** and a ratio which will serve as the **scaling factor**. Common values for these variables are the Roboto font family, a great choice due to its many different font weights, a starting font size of `18px` and a ratio of `1.618`, which is the golden ratio.
|
||||
|
||||
Based on these values, the basis of our typographic scale is an element with `font-size: 18px` and `line-height: 1.618`. Notice that the ratio is also applied to the line height. This allows the text to breathe a little more and makes it easier to read, while creating a consistent vertical rhythm.
|
||||
|
||||
Next, we are going to apply our ratio to scale the font up or down, as necessary. For example, we can multiply once by `1.618` for a sub heading, twice for a normal heading and three times for a large heading (e.g. our website's name on the home page). Similarly, we can scale the font down by dividing by our ratio to make elements less visually important (e.g. footnotes).
|
||||
Next, we are going to apply our ratio to **scale the font up or down**, as necessary. For example, we can multiply once by `1.618` for a sub heading, twice for a normal heading and three times for a large heading (e.g. our website's name on the home page). Similarly, we can scale the font down by dividing by our ratio to make elements less visually important (e.g. footnotes).
|
||||
|
||||
While this gives us a pretty solid setup, some elements might not look properly emphasized. To deal with this, we can use font weight to increase or decrease the visual importance of some elements. We could, for example, make headings bolder to make them more important. We could also make footnotes slightly bold to emphasize them as their font size is very small and they might get overlooked.
|
||||
While this gives us a pretty solid setup, some elements might not look properly emphasized. To deal with this, we can use **font weight** to increase or decrease the visual importance of some elements. We could, for example, make headings bolder to make them more important. We could also make footnotes slightly bold to emphasize them as their font size is very small and they might get overlooked.
|
||||
|
||||
Putting it all together, we should end up with a typographic scale that looks similar to this:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user