Posting to a Photo Blog on Netlify with Working Copy and Shortcuts

TL;DR

Using Working Copy and this iOS Shortcut, I take and edit photos on my phone and post them to my Photo Blog hosted on Netlify. See

Read more

Safari Won't Play Videos via React useEffect

TL;DR

View this example in Safari to see how playing a video via useEffect won’t work. If you’re attempting to make a video play inside a React effect, use useLayoutEffect.

useEffect vs useLayoutEffect

The React docs have the following tip that calls out when you should use useLayoutEffect vs useEffect:

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen. This makes your app feel more responsive. The majority of effects don’t need to happen synchronously. In the uncommon cases where they do (such as measuring the layout), there is a separate useLayoutEffect Hook with an API identical to useEffect.

In my experience, it’s pretty obvious when running an effect that should be a layout effect, such as the measuring example called out in the React docs. And the even though the behavior of useEffect is slightly different in timing from the previous componentDidMount and componentDidUpdate APIs, this advice makes a lot of sense and it is engrained in me now. When using hooks and writing effects, I always reach for useEffect first.

However, when developing livefromquarantine.club I found a case where it wasn’t obvious that I had to use useLayoutEffect instead of useEffect. I think my brain was holding on to ”useLayoutEffect is for measuring stuff!” and not realizing there are other events that need to be synchronous.

Read more

Announcing: Live From Quarantine Club

TL;DR

Check out livefromquarantine.club!

A project that started as a way to keep up with a daily live show during quarantine, and then morphed into a YouTube player that excels at playlists full of multi-song videos. It lets you shuffle, repeat, set up next songs, and share your current queue order with friends.

Technically, I had fun building it with @xstate/fsm and TypeScript and trying to keep the size as small as possible. I think the CSS is around 3kb and the JS is 22kb. I originally put it together as some spaghetti-code-script-tag-in-html proof-of-concept in a few hours. And then once I started hitting fun race condition bugs and couldn’t easily implement the features I wanted, I had to rewrite it.

Read more

Playing Around with Unique Random Keys

I had a project recently where I was trying to decide the optimal length for a nice human readable key, while making it unlikely to have collisions while keeping it semi-unguessable.

I wrote the code below so I could play around with different character sets, key lengths, and number of keys while seeing how those affected the timing and likelihood of collisions. The project allowed for retrying in the case of a collision, so this code below allows for those with a random delay to lookup whether a key has been taken already.

Read more

Linode StackScripts

My side project bracket.club is a mostly static, client heavy webapp, but still requires a few backend pieces to run during its production.

As a primarily frontend developer without a ton of spare time, I try to use as much hosted stuff as possible. I’m currently using a hosted Postgres database at Heroku and a hosted Node.js API at now.sh.

But it’s always nice to be able to run a VPS for somethings. In the case of bracket.club, there’s a few watchers to check for entries on Twitter and to get the latest results and save them to the DB. This is something that only needs to happen for a few days at a time and even on those days, the watchers might only need to run for a few hours.

This makes these watchers a perfect candidate for Linode’s hourly pricing. One of the caveats of hourly pricing though is that you are still charged for the instance even if it’s powered off or not in use. So it’s essential to be able to easily and effortlessly spin up and teardown these watcher instances in order to take full advantage of hourly pricing.

Read more