Most recent blog posts

Or you can click on the categories to filter by topic

An ideal pattern to combine react-router with tanstack/react-query

November 18, 2024
0 comments React, JavaScript

I'm writing this blog post from an admin interface I built, which is a web app frontend for the backend behind peterbe.com. It's built as a single-page app in Vite, with React.
Vite, unlike frameworks like Remix or Next, doesn't come with its own routing. You have to add that yourself. I added React Router. Another thing you have to do yourself is a way to load remote data into the app for display and for manipulation. This is done as XHR requests happen on the client side. For that, I chose TanStack Query. If you haven't used it but used React, it's sugar for this type of code:


// DON'T DO THIS. USE TANSTACK QUERY

const [stuff, setStuff] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
  fetch('/api/some/thing')
    .then(r => r.json())
    .then(data => setStuff(data)
    .catch(err => setError(err))
}, [])

Truncated! Read the rest by clicking the link below.

brotli_static in Nginx

November 8, 2024
0 comments Nginx, Linux

tl;dr; for Ubuntu (>=24.10) run apt install libnginx-mod-http-brotli-static

Nginx is the web server I use on my personal web server. It sits in front of static files and Python and Node backend servers.
For static files, you can make it server the files compressed with Brotli, if the equivalent file exists on disk as a .br file.
Suppose you have this:


server {
    root /path/to/my/app/dist;
    server_name myapp.example.com;

    brotli_static on; // THIS!

    location / {
        try_files $uri /index.html;
    }

    ...
}

Truncated! Read the rest by clicking the link below.

Object.keys to return the known strings of an object in TypeScript

October 25, 2024
0 comments JavaScript

This one always confused me but by blogging about it, hopefully, it will stick.

This does NOT work in TypeScript:


const person = {
  firstName: "peter",
  lastName: "bengtsson",
  state: "happy"
}

const keys = Object.keys(person)
const randomKey = keys[Math.floor(Math.random() * keys.length)];
const value = person[randomKey]

It works in JavaScript, but in TypeScript you get an error on the last line (from the person[randomKey]):

Truncated! Read the rest by clicking the link below.

How I make my Vite dev server experience faster

October 22, 2024
0 comments React, Node, JavaScript

I have a web app that operates as a SPA (Single Page App). It's built on regular Vite + React + TypeScript. In production you just host the built static assets, but in development (for hot module reloading and stuff) I use the built-in dev server in Vite. This is what you get when you type vite without any other command line arguments.

But here's what happens, you're in the terminal and you type npm run dev and hit Enter. Then, using your trackpad or keyboard shortcuts you switch over to a browser tab and go to http://localhost:5173/. When you get there to the first request, Vite starts at the entry point, which is src/main.tsx and from there, it looks at its imports and starts transpiling the files needed. You can see what happens with vite --debug transform.

With debug:

Truncated! Read the rest by clicking the link below.

How to extend a function in TypeScript without repeating the signature types

October 16, 2024
0 comments JavaScript

Suppose you have a function, written in TypeScript, that is fine and complete. Now you want to write a function that works like that one but with just some slightly more parameters and other differences. It's not too different from a decorator function.

Extending the list of parameters

Let's imagine that you already have this function:


type Operation = "sum" | "sub" | "mul" | "div"

function calculator(a: number, b: number, op: Operation) {
  if (op === "sum") return a + b
  if (op === "sub") return a - b
  if (op === "mul") return a * b
  if (op === "div") return a / b
  throw new Error(op)
}

And this could, for the sake of argument, be in a different file and/or possibly outside your control. What you now want to do is write a new function that takes those same arguments, plus one more. And importantly, you don't want to have to spell out all the arguments one more time.

First, the WRONG way to solve this:


import { calculator } from './calculator' // The code in the snippet above

type Operation = "sum" | "sub" | "mul" | "div"

function doubleCalculator(a: number, b: number, op: Operation, c: number) {
  return calculator(calculator(a, b, op), c, op)
}

console.log(calculator(1, 2, "sum")) // 3
console.log(doubleCalculator(1, 2, "sum", 10)) // 13

Technically, it works, but you had to repeat the calculator function's signature.

Truncated! Read the rest by clicking the link below.

Trying out the new Bun "Compile to bytecode"

October 15, 2024
0 comments Bun, JavaScript

Bun 1.1.30 came out last week. What got me intrigued is this new option to bun build which is --bytecode. With it you can create an executable, supposedly compiled partially to bytecode which means it can start even faster.

I tried it on my hylite CLI which is a CLI, built with Bun, but works in all versions of Node, that can syntax highlight code to HTML on the command line. Here's what I did:

Truncated! Read the rest by clicking the link below.

The performance benefits of code-split an SPA

October 12, 2024
0 comments React

This isn't a comprehensive in-depth analysis but I have this SPA which is built with Vite + React.
When you run npm run build it produces:

vite v5.4.8 building for production...
✓ 8210 modules transformed.
dist/index.html                             0.76 kB │ gzip:   0.43 kB
dist/assets/images-xTxpPavl.css             2.02 kB │ gzip:   0.55 kB
dist/assets/index-IHK6QBxo.css            200.12 kB │ gzip:  29.85 kB
dist/assets/index-jgmGYYS9.js               0.79 kB │ gzip:   0.51 kB
dist/assets/open-graph-image-Ca6hLYnz.js    1.47 kB │ gzip:   0.82 kB
dist/assets/images-CwbhV2EW.js             28.75 kB │ gzip:  10.37 kB
dist/assets/pageviews-C6NSq649.js         378.67 kB │ gzip: 106.42 kB
dist/assets/index-HpyQl1NK.js             490.15 kB │ gzip: 154.11 kB
✓ built in 4.46s

Truncated! Read the rest by clicking the link below.

Rate my golf swing (October 2024)

October 11, 2024
0 comments Golf

Last time I uploaded a video was only a few months ago but one thing that I've improved, since that video, is the wrist alignment at the top of the swing. Neither video does a perfect job of showing this but it's a least a bit better now.

Top of swing highlighted

I think what I still have to work on to slow down, and find a smooth and slow tempo, as the club travels up in the back swing.

The 3 queries I use with pg_stat_statements to analyze slow PostgreSQL queries

September 30, 2024
0 comments PostgreSQL

pg_stat_statements is a contributed extension for PostgreSQL that logs all queries and how long they took. If you aggregate on this, you can determine where your PostgreSQL spends the most time and what to optimize.

How it works and how you install it is for another blog post.

Total time


SELECT
  (total_time / 1000 / 60) AS total,
    (total_time/calls) AS avg, calls,
      SUBSTRING(query FROM 0 FOR 250)
      FROM pg_stat_statements
      WHERE calls > 100
      ORDER BY 1 DESC
      LIMIT 25;

This one is important because you could have some terribly slow query that uses lots of sequential scans, but perhaps it's only used once a week, so who cares?

Truncated! Read the rest by clicking the link below.

How to handle success and failure in @tanstack/react-query useQuery hook

September 16, 2024
0 comments React, JavaScript

What @tanstack/react-query is is a fancy way of fetching data, on the client, in a React app.

Simplified primer by example; instead of...


function MyComponent() {
  const [userInfo, setUserInfo] = useState(null)
  useEffect(() => {
    fetch('/api/user/info')
    .then(response => response.json())
    .then(data => {
      setUserInfo(data)
    })
  }, [])

  return <div>Username: {userInfo ? userInfo.user_name : <em>not yet known</em>}</div>
}

Truncated! Read the rest by clicking the link below.

Previous page
Next page