Tips for concatenating URLs in JavaScript

· JavaScript · Patrick Smith

You might have a base URL from configuration, that you then concatenate with paths to produce a full URL. Say an API URL you fetch from.

But should you provide the base URL with or without a trailing slash? e.g. Should you have https://api.example.org/ or https://api.example.org?

If you simply combine the strings together, you can end up with double slashes:

const baseURL = 'https://api.example.org/';

const productsURL = baseURL + '/products';
// 'https://api.example.org//products'

We could try and check for the double slash, but that’s a pain:

const productsURL = baseURL +
(baseURL.endsWith('/') ? 'products' : '/products');

Would you just rather not worry about this? Turns out we can with the built-in URL class. It’s supported in all modern browsers (not IE).

const baseURL = 'https://api.example.org/';

const productsURL = new URL('/products', baseURL).toString();
// 'https://api.example.org/products'

Isn’t that better? It’s also available in Node.js and Deno, so we can use the same technique everywhere.

Tim Bray on subscribing to publications

· Product · Patrick Smith

Tim Bray writes why he won’t subscribe:

Their arithmetic didn’t consider their chance of getting me to click on “Subscribe.” In my particular case, that chance is almost exactly Zero. I subscribe to enough things and I am acutely reluctant to give anyone else the ability to make regular withdrawals from my bank account. … It’s exactly because I’ve done some subscribing that I’m just not gonna do any more.

Biasing your business to producing value in a public manner

· Marketing · Patrick Smith

Some ancient 2011 advice from Patrick McKenzie:

Businesses create value with almost everything they do.  The lion’s share of the value is, like an iceberg, below the waterline: it cannot be seen from anywhere outside the business.  Create more value than you capture, certainly, but get the credit for doing so, both from Google and from everybody else.  This means biasing your business to producing value in a public manner.  Did you write software not core to  your line of business?  Great, OSS it.  Get the credit.  Have you learned something novel about your customers or industry?  Write about it.  Get the credit.  Are your business’ processes worthy of emulation?  Spread what you know.  Get the credit.

37Signals is amazing at this. You can do it, too.

Get good at SEO — You need more links. Create ways to justify people who aren’t in a commercial relationship with you linking to you anyway. My favorite way for doing this is getting the credit for things you do, as described above.

https://www.kalzumeus.com/2011/06/17/software-businesses-in-5-hours-a-week-microconf-2010-presentation-1-hour/

There are also slides for the talk.

My most used commands for front-end

· JavaScript, Workflow · Patrick Smith
# Create a new TypeScript library with TSDX
npx tsdx create <project-name>

# Create a new web app with Vite
npm init vite-app <project-name> # Vue by default
npm init vite-app <project-name> --template preact
npm init vite-app <project-name> --template react

# Measure HTTP response time
httpstat "https://example.org/"

# Run Google’s PageSpeed Insights
npx psi "https://example.org/" --strategy=mobile

# Optimize & minify SVG file
npx svgo <some-file.svg>

When a third party library schedules a focus via requestAnimationFrame() and blows all your tests up

· JavaScript, React, Testing · Patrick Smith

The Reach UI components are great. They offer accessible components that you’ll often need when building web apps — such as modals, menus, tabs and more. It’s also popular and therefore has been battled-tested to work with a range of clients.

However, needing to provide accessibility in any project, it sometimes has to make tradeoffs. One decision the @reach/menu-button package has is when to focus on the menu when opening. It’s not focused immediately — it’s asynchronously focused on the next frame. Here’s what they do:

  // When the menu is open, focus is placed on the menu itself so that
  // keyboard navigation is still possible.
  useEffect(() => {
    if (state.isExpanded) {
      // @ts-ignore
      window.__REACH_DISABLE_TOOLTIPS = true;
      window.requestAnimationFrame(() => {
        focus(menuRef.current);

This leads to errors when running Jest:

console.error
Warning: An update to PopoverImpl inside a test was not wrapped in act(…).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://fb.me/react-wrap-tests-with-act

We can’t change the implementation, so our tests are forced to come up with a solution.

We need to wait to the next frame. We can do this by running requestAnimationFrame() ourself.

However, when testing React we need to ensure anything that could change state is wrapped in act() so that it has rerendered. This function accepts a callback that returns a Promise (also known as an async function). So first we wrap requestAnimationFrame() in a Promise, then wrap that in act() — let’s call this waitForNextFrame() like so:

export function waitForNextFrame(): Promise<undefined> {
  return act(() => {
    return new Promise((resolve) => {
      requestAnimationFrame(() => resolve());
    });
  });
}

Now we can run this after clicking on our menu:

import { render, screen } from "@testing-library/react";
import user from "@testing-library/user-event";

beforeEach(async () => {
  render(<ComponentWithMenu />);
  user.click(screen.getByRole('button', { name: 'Open menu' });
  await waitForNextFrame();
});

We could also defensively run this after every test so any animation frame callbacks have been run:

afterEach(waitForNextFrame);