Artiklar

december 2022
Share Types between backend and frontend in a monorepo
To avoid creating duplicate Interfaces and Types in your backend and frontend for DTO´s, parameters and you name it; we can share those types with TypeScripts: Path mapping.
Add a custom path in your frontend/client-side tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@backend/*": ["../server/src/types/*"], },
}
}
- The path is going up a directory from your
baseUrl
and then going toserver/src/types
-directory. - We add * as a wildcard to mean that everything in that directory is to be accessible.
- We call our path
@backend
. This will be used as an alias when using ES6-imports.
Types are now available to be imported from the server-side with ES6-imports:
import { ItemDTO } from "@backend/itemDTO.js"
// the relative server-side path: ./src/types/itemDTO.js
Iconify: en Open Source bibliotek av ikoner
Bläddra bland hundratals diverse ikongrupper från alla möjliga ikonskapare och ladda ned dem som SVG - eller - importera som en komponent i ditt ramverk (exempelvis: React).
Share variables between middlewares in Express
Express has object which can be used to store and access properties, either globally, or within a single request-response cycle.
- Read and write to
app.locals
-object to share data globally, between middleware, during the life-time of the application.
// Write
app.locals.title = 'Kwik'
app.locals.domain = 'kwik.se'
// Read
app.locals.title // 'Kwik'
app.locals.domain // 'kwik.se'
- Read and write to
res.locals
-object to share data within a single HTTP-request method.
// Write
res.locals.user = 'Mervin'
res.locals.email = 'mervin@kwik.se'
// Read
res.locals.title // 'Mervin'
res.locals.email // 'mervin@kwik.se'
Using middleware in Express
Middlewares are functions that do a specific task, in a specific order and hand over to the next middleware once done. How can we use this?
Middlewares are usually introduced to a developer when needing a third-party solution to be used globally with app.use()
. This post is about using middlewares on a single HTTP-request method.
Middlewares are functions called between a request and your final response. The simplest form of a middleware is the arrow function that responds with "Hello World" from a GET-request:
app.get("/", (res, req) => {
res.send("Hello World")
})
// This arrow function is the middleware used above
(res, req) => { res.send("Hello World") }
Express considers every argument after the initial path-argument to be middleware. There is no limit to how many middlewares you have.
What is important to note is that each middleware hands over the request to the next middleware once done. Because Express
does not know when the middleware is done with the task; it listens for next()
. For the middleware to use next()
, it has to add next as it's last parameter. Middleware that is last in the chain can omit the usage of next()
.
app.get("/", (req, res, next) => {
/* check something */
next()
}, (req, res) => {
res.send("Hello World")
})
// These arrow functions are the middleware used above
(req, res) => { /* check something */; next() }
(req, res) => { res.send("Hello World") }
A better solution is to define the functions and call them by their name instead of writing whole functions inside of our app.get
parentheses.
// These functions are the middleware used below
const firstMiddleware = (req, res, next) => { /* check something */; next() }
const secondMiddleware = (req, res) => { res.send("Hello World") }
app.get("/", firstMiddleware, secondMiddleware)
A boilerplate Node.js config
Here is a simple boilerplate configuration when using Node.js with Typescript using ES-modules and reloading it with Nodemon.
The configuration
package.json
{
"main": "index.ts",
"type": "module",
"scripts": {
"watch": "tsc -w",
"dev": "nodemon"
},
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2"
},
"devDependencies": {
"@types/cors": "^2.8.13",
"@types/express": "^4.17.15",
"nodemon": "^2.0.20",
"typescript": "^4.9.4"
}
}
As TypeScript is globally installed on my VS Code IDE, there is no need to add it as a devDependency, but you should if you do not have it installed at all.
tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"rootDir": "./src",
"moduleResolution": "NodeNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"baseUrl": "./",
"outDir": "./dist",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"strictNullChecks": true,
"alwaysStrict": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"jsx": "react-jsx"
},
"include": ["./src/**/*.ts"],
"exclude": ["node_modules"],
}
nodemon.json
{
"watch": ["dist"],
"ext": "js,json",
"ignore": [],
"exec": "node ./dist/index.js"
}
The execution
Having your project run is done in two-and-a-half steps.
- We must compile TypeScript into JavaScript..
.. (and tell it to re-compile every time we save a change in our TypeScript-files)
// execute in a terminal-window
npm run watch
// it will do whatever "watch" is told to do in package.json
- We must tell Nodemon to run our app..
.. (and tell it to re-run it every time it notices that any .js or .json file has been changed)
// execute in a new terminal-window
npm run nodemon
// it will do whatever nodemon.json has stated
Reacts createPortal får DOM-element att teleportera
createPortal()
i React är vad .appendChild()
är i vanilla JavaScript. Skillnaden är att elementet i React inte har en aning om det!
Funktionen importeras utav react-dom
och tar emot två parametrar. Första parametern tar emot innehållet (JSX) man vill teleportera, och andra parametern tar emot destinationen (DOM-föräldern den ska befinna sig under).
import { createPortal } from 'react-dom';
// ...
{createPortal(
<span>To the moon!</span>,
document.body
)}
Funktionen måste köras innuti ett JSX-element eftersom den fortfarande ser sig som en del av trädet. Och det coola är att själv teleporterade elementet fortfarande tror att den är kvar i sin originala JSX-position; vilket innebär att den fortfarande bubblar uppåt, tar emot props från sin ursprungliga förälder och så vidare.
Anvädningsområden
- Modal
- Skapa självständiga modulära React-kompontenter kopplade mot valfria DOM-element och bortkopplade från varandra.
- Skapa React-komponenter som kopplas på DOM-element som ursprungligen inte är en del av React-trädet.
Can your variable be multiple types? Use a ternary operator in your JSX
Typescript will warn you if a variable has the potential to return more than one option and require that you extend your codebase to cover all potential cases. A ternary operator is a great solution for this.
In this example, our return is either a string or undefined.
type AcceptedReturn = string | undefined
const data: AcceptedReturn = getUnknownData()
A good and acceptable approach would be to have separate if-statements:
if (!data) return <span>There is no data.</span>
return <span>Here is your data: {data}<span>
A better approach would be to use a ternary operator:
data
? <span>Here is your data: {data}<span>
: <span>There is no data.</span>
It is also common to use an &&-operator if there is no need for a fallback return, but there are some potential bugs with this approach:
data && <span>Here is your data: {data}<span>
// There is a possibility of a bug with this approach.
// Example of a bug can be read about at: https://kentcdodds.com/blog/use-ternaries-rather-than-and-and-in-jsx
unDraw: en Open Source bibliotek av SVG-illustrationer
Bläddra bland hundratals diverse SVG-illustrationer och ladda ned dem som passar i valfri färg.
Zod is a one-stop solution to validate and type declare your data
Build a validation-template for your specific data and Zod can use that template/schema to create a typescript Type.
Reason why it is good practice to have your data funneled through a type-check AND a validator is because:
- Typescript can catch mistakes straight in your editor based on your description of the data.
- Validator can catch mistakes when your program runs and the actual data is different than what you are expecting.
An example of a simple work flow using Zod:
- Create a validation template/schema to be used on your specific data
const validation = z.object({
name: z.string(),
id: z.number().positive(),
job: z.string().optional()
})
- Create a typescript Type based on the schema you declared above
type useTypeFromValidation = z.infer<typeof validation>
- Add type-checking ability for your data
// our data with assigned Type based on (infered from) our schema
const data: useTypeFromValidation = { name: "Mervin", id: 1 }
- Check if the actual data follows the outlined schema before working on it
const result = validation.safeParse(data);
if (!result.success) {
// handle error then return
result.error;
} else {
// do something
result.data;
}
Importing dotenv the ES6-modules way
The dotenv documentation state that importing dotenv should be done as following:
import * as dotenv from 'dotenv'
dotenv.config()
- The first line above imports node_modules/dotenv.
- The second line would fetch your custom .env-file into that specific module.
There is another way loading your custom .env-file into your project immediately:
import 'dotenv/config`
Using dotenv-variables after importing the dotenv/config:
// Destructure process.env object
const { env } = process
// Call your specific .env-variables
env.mySecretVariable
...
env.API_KEY
Visa upp en självöversatt och -formaterad lista med Intl.ListFormat
Oftast listar man en Array genom att använda sig utav .join(). Därefter behöver logik appliceras för att inte ha någon släpande karaktär - ett kommatecken, exempelvis.
Det finns dock redan en metod man kan använda via det integrerade Intl-objektet som ordnar:
- Uppdelning av element
- Infogar ett skiljetecken
- Bifogar ett "och", "eller" inför sista elementet
- Översätter själv till valt språk
const cities = ["Stockholm", "Göteborg", "Malmö"]
const listAnd = new Intl.ListFormat('sv', { style: 'long', type: 'conjunction' })
listAnd.format(cities) // Stockholm, Göteborg och Malmö
const listOr = new Intl.ListFormat('sv', { style: 'long', type: 'disjunction' })
listOr.format(cities) // Stockholm, Göteborg eller Malmö
Det går ej att själv-referera i ett oinitierat objekt
En påminnelse om att man inte kan själv-referera till en prop (property/key) i ett objekt som ej är skapat/initierat.
// Detta fungerar ej
obj = {
a: 2,
b: 3,
c: this.a + this.b
}
Man ska istället skapa en "getter" (funktion) som kommer åt referenserna (genom att utnyttja konceptet; closure).
// Detta fungerar
obj = {
a: 2,
b: 3,
get c() { return this.a + this.b }
}
november 2022
CORS-issues? Use a proxy
CORS is a safety-guard only applied on browsers. There are no CORS-errors if requesting from a server.
For serious projects, create your own proxy (by using Express, as example).
For testing, use a remote proxy.
List of a few remote proxies:
Just wrap your request with their proxy.
useContext() i React för ljus/mörk-tema
UseContext() är det bästa sättet att upplysa root-elementet om aktuell status från en komponent djupt inne i trädet.
1. Skapa en kontext som egen funktion
// file: /hooks/themeContext.js
import { createContext } from 'react';
export const useDarkMode = createContext();
/////////////////////////////
2. Väv in kontextet i root-Elementet
// file: App.jsx
import { useState } from 'react'
import { useDarkMode } from './hooks/ThemeContext'
function App() {
const [darkMode, setDarkMode] = useState(false)
<useDarkMode.Provider value={[darkMode, setDarkMode]}>
<div data-theme={darkMode ? "dark" : null}></div>
</useDarkMode.Provider>
}
/////////////////////////////
3. Upplys root-Elementet om status djupt inifrån
// file: /pages/header/RightNav.jsx
import { useContext, useState } from "react";
import { useDarkMode } from "../../hooks/ThemeContext"
function RightNav() {
const [darkMode, setDarkMode] = useContext(useDarkMode);
<button onClick={() => setDarkMode(!darkMode)}></button>
}
SCSS är ett bättre CSS
SCSS är, och skrivs som, CSS men bidrar med extra nyttiga funktioner.
// OBS! För att kunna använda .scss-filer i projekt, behöver man installera Sass (som processerar och konverterar .scss-filer till vanligt .css åt webbläsaren).
npm i sass
-
SCSS skrivs med likadan syntax som CSS
-
SCSS accepterar // kommentarer i sina filer, likt JavaScript
-
S.k. "nesting" av barn är tillåtet vilket sparar på duplicering av kodblock
// CSS .parent { color: red; } .parent .child { color: blue; } // SCSS .parent { color: red; .child { color: blue } }
-
Barn som delar namn med sina föräldrar kan skrivas ut med ett &-tecken
// CSS .parent { color: red; } .parent .parent-child { color: blue; } .parent .parent-child:hover { color: green; } // SCSS .parent { color: red; &-child { color: blue; } &:hover { color: green; }
Vue: share data between child and parent
To prop drill from parent into child; parent should add an "attribute" to the child's component-element.
// Child component
props: [ "title" ]
// Parent component
<Child title="this is a static string" />
...or...
<Child :title="this-is-a-variable" />
To bubble a value up from a child into parent; child should emit (signal out) it's value to the parent.
// Parent component
<Child @name_of_this_signal="(arg_from_child) => { return arg_from_child }"
// Child component wants to send an input's value
<input @click="$emit('name_of_this_signal', $event.target.value)" />
ReactRouter fungerar inte med hashade länkar
Det finns ett bra npm-paket som smidigt löser problemet.
import { HashLink } from 'react-router-hash-link';
<HashLink to="/#part-of-page">Part of page</HashLink>
Show Unicode-pairs as icon by using fromCodePoint
Keep in mind that Unicode has to be changed into a combination that JavaScript is able to read.
A unicode of U+1F1EC becomes 0x1F1EC. In other words, the front-two characters are replaced from U+ to 0x.
The rest is quite straightforward as seen below:
// original unicodes: U+1F3F3 U+FE0F U+200D U+1F308
const icon = String.fromCodePoint(0x1F3F3, 0xFE0F, 0x200D, 0x1F308)
console.log(icon) // 🏳️🌈
Blogs to follow
Just a simple list of blogs I find net-positive for myself and want to give credit where credit is due.
- GoMakeThings (daily tips)
- From Cook to (great) Developer
- Bartosz Ciechanowski (explaining topics with JS-animations)
- joshWcomeau (informative React knowledge)
- Flavio Copes
- HTMHell (about bad code and how to fix it)
Also some resources worth knowing:
Skapa en relativ sökväg av enbart filnamnet
function getImageUrl(name) {
const dir = "./uploads/images/"
return new URL(`${dir}${name}`, import.meta.url).href
}
// exempel på användning
getImageUrl("bild.jpg")
januari 2024
Use obj[variable] without Typescript causing an issue with "type assertion"
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type. What does this even mean and how to fix it?
Bobby writes in more detail about this issue on his blog: https://bobbyhadz.com/blog/typescript-element-implicitly-has-any-type-expression
TypeScript is basically telling us that the string
type is too broad for it's liking because not all potential strings in the object are necessarely keys — so we have to convince it otherwise; that this is, indeed, one of the object's keys.
const customerInfo = {
first_name: { ... }
last_name: { ... }
...
}
const contactForm = {
first_name: {
label: "First name",
value: null
}
}
Object.entries(contactForm).forEach((props, key) => {
if (props.value === null) {
const storedValue = customerInfo[key as keyof typeof customerInfo]
}
}
The secret sauce is telling Typescript that the index (key) indeed is one of whatever keys the obj contains by using the typeof keyof
type assertion.
Change color on an SVG with CSS
Often I find myself wanting to change a SVG's color (fill) on hover; sometimes it works - sometimes it does not. The solution: currentColor.
Change SVG's fill-value into "currentColor". It will then inherit whatever color your font-family uses.
<svg width="1em" height="1em">
<path fill="currentColor" d="m1022.74 942..."></path>
</svg>
You target changes in CSS by:
svg {
color: red;
}
januari 2023
Skapa unika strängar med Crypto
React kräver ett unikt ID-nummer för varje enskilt element skapat med en loop. Webbläsaren och Node.js har ett Crypto-interface vi kan använda oss utav.
I Node.js får man fram ett unikt ID-nummer på följande sätt:
import * as nodeCrypto from "node:crypto"
const unqiueID = nodeCrypto.randomUUID()
I webbläsaren får man fram ett unikt ID-nummer på följande sätt:
const unqiueID = crypto.randomUUID()
Transform a breadcrumbs-string into a parent-linked data structure
Create a hierarchy of child/parent-menu items from a string of breadcrumbs using Map().
I scraped breadcrumbs of a e-commerce site, to use as a menu-template for my own e-commerce project, and got a list of strings like this:
const scrapedCategories = [
"Möbler",
"Möbler - Förvaring",
"Möbler - Belysning",
"Möbler - Belysning - Bordlampor",
"Möbler - Belysning - Taklampor",
"Möbler - Belysning - Utebelysning",
"Möbler - Hallmöbler",
"Möbler - Hallmöbler - Byråer",
"Möbler - Hallmöbler - Bänkar",
]
My goal was to have an array with all individual menus containing their name and their parent. If they did not have a parent; contain null.
// My goal:
const finalVersion = [
{
"menu": "Möbler",
"parent": null
},
{
"menu": "Belysning",
"parent": "Möbler"
},
{
"menu": "Bordlampor",
"parent": "Belysning"
}
]
First step is to split the initial strings into an array of individual elements. I reverse the array so that I get the deepest sub-category first in line, which will help in the next step with recursion.
const stringToArray = scrapedCategories.map(string => {
return string
.split(" - ")
.reverse()
})
Recursion is used to re-call the same function until JavaScript stumbles across your stop condition. It fits great with my part-goal to chain a sub-category to it's parent, regardless of levels nested.
function chainToParent(array) {
if (!array.length) return null
return {
menu: array.at(0),
parent: chainToParent(array.splice(1))
}
}
The function above will transform an array into an object of two properties.
- Menu-property containing the first element of the array.
- Parent-property containing an object holding two properties (menu and parent).
The parent property is created using recursion with the goal to recreate the top-level logic - and going deeper until it hits the stop condition.
const arrayToList = chainToParent(["Bordlampor", "Belysning", "Möbler"])
// result = {
menu: 'Bordlampor',
parent: {
menu: 'Belysning',
parent: {
menu: 'Möbler',
parent: null
}
}
}
Map()
is a great way get flattened results of child<->parent dependency. Using Map()
and yet another function with recursion takes us all the way to the goal.
const listOfMenuParents = new Map()
function flatten(node) {
if (!node.parent) {
return listOfMenuParents.set(node.menu, null)
}
listOfMenuParents.set(node.menu, node.parent.menu)
return flatten(node.parent)
}
Executing the flatten-function above and reading the Map() gives us the following:
arrayToList.forEach(chainedList => {
flatten(chainedList)
}
// Result: {
'Möbler' => null,
'Belysning' => 'Möbler',
'Bordlampor' => 'Belysning',
}
This can be written shorter without temprary variables but opted in for a more descriptive (verbose) explanation.
ES6-way to read absolute path Node.js
Getting the directory path using the CommonJS-module system does not work when working with ES6-modules. There is however a workaround.
import path from "path"
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename)
-
Resolve the path if in need of the directory-path of the parent
const parentDirName = path.resolve(__dirname, '..')
-
Cconcatenate if in need of the directory-path of child
const childDirName = __dirname + "/child"
Höjden 100vh gäller inte på iPhones, använd -webkit-fill-available!
iOS har ett adressfält som gömmer den nedersta delen av hemsidan. Genom att använda en Apple/webkit-specifik CSS-egenskap så kan man rätta till felet.
CSS-flex är ett perfekt redkspa för att få till en header och footer som den fasta högsta och lägsta delen av den synliga hemsidan.
HTML
// HTML
<body>
<header>Högst upp</header>
<main>Huvudinnehåll</main>
<footer>Längst ned</footer>
</body>
CSS
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
}
Problemet uppstår enbart på iOS-enheter och innebär att footer-elementet göms under Safaris adressfält. Genom att använda en Safari-bekant CSS-egenskap -webkit-fill-available
kan man tvinga Safari till att exkludera ytan under adressfältet som tillgängligt.
CSS med buggfixen
body {
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: -webkit-fill-available;
}
main {
flex: 1;
}
Uppdatering (2024-01-17)
Nu kan man använda svh
(small viewport height)-unit för att exkludera webbläsarens adressfält.
body {
display: flex;
flex-direction: column;
min-height: 100svh;
}
main {
flex: 1;
}
URLSearchParams converts JavaScript data types into a URL-encoded string
Query transmitted as part of the URL cannot contain specific characters. It must therefore be transmitted with URL encoding.
Thankfully, we can use URLSearchParams
to convert a ordinary string, object or array into a URL-encoded string with appropriate seperators (&
).
const params = {
name: 'Mervin Bratic',
height: 193,
}
const URLencodedString = new URLSearchParams(params).toString();
console.log(URLencodedString) // 'name=Mervin+Bratic&height=193'
Snap a user-scroll smoothly to specific points with CSS
Guide the scroll to continiously stop at your HTML-element(s) upon users scroll och finger-swipes. Works horizontally as vertically.
The container-element needs scroll-snap-type
inserted into it's style. Children need scroll-snap-align
inserted into their style.
// HTML
<div class="container">
<div class="child">First</div>
<div class="child">Second</div>
</div>
// CSS
.container {
scroll-snap-type: y mandatory;
}
.child {
scroll-snap-align: start;
}
-
Container has multiple scroll-snap types/properties to choose from. They are divided as:
- Behaviour:
Mandatory
orproximity
- Direction:
x
,y
,block
,inline
orboth
- Behaviour:
-
Example above wants the scroll-snap to be for vertical (y) scrolling and align at childs position.
-
Child has multiple scroll-snap-align properties to choose from. They are divided as:
none
,start
,center
,end
-
Example above wants the scroll to stop at the beginning of each child-element.
Hitta specifik förfader med rekursion
Om man är i behov att manipulera ett element som är flera led ovanför elementet man har tillgång till så kan man använda sig utav en rekursiv funktion för att hitta rätt.
type FuncType = (element: HTMLElement, parentClass: string) => HTMLElement | null
const findParentByClass: FuncType = (element, parentClass) => {
const parent = element.parentElement
if (!parent || parent.tagName === "Body") return null
if (parent.className.includes(parentClass)) return parent
return findParentByClass(parent, parentClass)
}
// Exempel
const startingPoint = document.querySelector("#item1")
findParentsByClass(startingPoint, "gallery")
- Som svar får man ett HTMLElement som innehåller del av klassnamnet - eller - null.
- Den stannar av sökandet om den känner av Body-elementet.
- Fungerar även med SCSS som döper om till unika klasser, eftersom kärnan av klassnamnet ändå är kvar.
december 2023
Free email-hosting with own domain using Zoho Mail
I host my website (kwik.se) on Netlify with their free tier - as most of us do. For hosting my custom e-mail (@kwik.se), I use Zoho's free-forever tier.
Your domain provider might want to upsell you web-hosting and e-mail hosting, but there are free alternatives out there. As mentioned: Netlify is a great choice for hosting your projects, and Zoho handles e-mails just fine.
Sign up for Zoho Mail (free plan):
https://mail.zoho.com/signup?type=org&plan=free
You then have to access and change your DNS-records - available on your accounts-page at your domain provider (one.com, for example). These DNS-records will need to your custom domain toward the hosting provider you want to use. Zoho guides you step-by-step through this process, and it is basically a copy-paste job adding 4-5 MX and TXT rows to your DNS-record.
Zoho allows five (5) e-mail aliases to receive and send e-mails from, meaning that you can have your info@, name@, contact@, no-reply@ and order@ looking like a professional.
Your e-mail can be accessed via the browser (https://mail.zoho.eu) or by their own Zoho Mail-app.