Today I Learned

A Hashrocket project

23 posts by mattblack @mattblackdev

Typescript Bang

The “non-null assertion operator” or “!” at the end of an operand is one way to tell the compiler you’re sure the thing is not null, nor undefined. In some situations, the compiler can’t determine things humans can.

For example, say we have a treasure map with some gold in it.

type TreasureMap = Map<string, number>

const map: TreasureMap = new Map()
const treasure = 'gold'
map.set(treasure, 10)

And we’re cranking out some code that tells us if

function isItWorthIt(map: TreasureMap) { 
  return map.has(treasure) && map.get(treasure) > 9
}

Obviously we are checking if the map has gold first. If it doesn’t, the execution returns early with false. If it does, then we know we can safely access the value.

But the compiler gets really upset about this:

Object is possibly ‘undefined’. ts(2532)

In this case, the compiler doesn’t keep map.has(treasure) in context when evaluating map.get(treasure). There is more than one way to solve this, but for now we can simply “assert” our superiority over the compiler with a bang.

return map.has(treasure) && map.get(treasure)! > 9

Typscript Docs - non-null-assertion-operator

Generic React Components

When props are generic like this:

inteface SelectProps<T> {
  options: T[];
  onChange: (value: T) => void;
}

function CoolSelect<T> (props: SelectProps<T>) {
    // ...
}

The generic part can be specified in JSX like this:

interface Fruit {
  name: string;
  isFruit: boolean
}

const fruits = [
  { name: 'Pumpkin', isFruit: true },
  { name: 'Avocado', isFruit: true },
  { name: 'Cucumber', isFruit: true },
  { name: 'Bell Pepper', isFruit: true },
]

funciton App() {
  return <CoolSelect<Fruit> options={fruits} />
}

See it? <CoolSelect<Fruit> options={fruits} />

Now when crafting the onChange function in this example, it’s type will be infered as this:

type OnChange = (value: Fruit) => void
funciton App() {
  return (
    <CoolSelect<Fruit>
      options={fruits}
      onChange={value => {
        if (value.isFruit && value.name === 'Bell Pepper') {
          console.log("You're blowing my mind dude!")
        }
      }}
    />
  )
}

*This syntax is available in Typescript v2.9+

Throttle iOS simulators' network speed

Get MacOS’s Network Link Conditioner.prefPane.

  1. Sign in at apple: https://developer.apple.com/download/more/?=Additional%20Tools%20for%20Xcode
  2. Download “Additional Tools for Xcode [version]” for your Xcode version.
  3. Run the DMG
  4. Open Hardware and double-click Network Link Conditioner.prefPane to install.

Use it:

  1. Open Settings then Network Link Conditioner
  2. Adjust the Profile and toggle the switch ON

Remember to turn it off :)

How to fight the dragon

Tonight I learned how to fight the dragon in #Minecraft.

Preparation

Nice to have:

  • armor with protection
  • bow with power
  • sword with sharpening
  • stength/health/regen potions
  • pumpkin head
  • snowballs

Must have:

  • golden apple, speed potion,
  • pickaxe, bow, arrows, sword & shield, armor
  • stack of dirt/cobblestone, buckets of water, ladders

I proceeded to The End. I picked a fight with a dragon and its pesky posse of ender men (pumpkin head helps). I learned to:

  • dodge the purple dragon breath
  • dodge or block dragon dives with the sheild
  • destroy crystals

    • with bow or snowballs for shorter towers
    • for taller towers, dump water at tower base, climb with ladder, then break crystal and fall into water
  • with a sword strike dragon’s feet when it sits on the pillar of bedrock
  • use looted ender pearls to teleport dodge

In The End, I died.

Smooth animations with Math.sin()

Remember geometry class? Well, I don’t.

But I do know that when you’re making a synthesizer, a sine wave produces a nice smooth sound.

And as it turns out, a sine wave has a smooth curve. So, you can use Math.sin() to animate smooth motions like so:

const domNode = document.querySelector('#waveEmoji')
function animate() {
  const a = Math.sin(Date.now() / speed) * range + offset;
  domNode.style = `transform: rotate(${a}deg);`;
  requestAnimationFrame(animate);
}
animate();

Edit Sine Wave

Import Absolute Paths in Typescript Jest Tests

In order to avoid this:

// project/__tests__/stuff/someDistantCousin.test.ts
import { thing } from '../../src/stuff/someDistantCousin'
import { wrapFunction } from '../testUtils/firebase'

And to write this instead:

import { thing } from 'src/stuff/someDistantCousin'
import { wrapFunction } from 'tests/testUtils/firebase'

There are 2 things to configure:

  1. Jest Config (i.e. jest.config.js, package.json, etc…)
  2. Typscript Config: tsconfig.json

jest.config.js

module.exports = {
  moduleNameMapper: {
    'src/(.*)': '<rootDir>/src/$1',
    'tests/(.*)': '<rootDir>/__tests__/$1',
  },
}

tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "src/*": ["src/*"],
      "tests/*": ["__tests__/*"]
    }
  }
}

Do not prefix TypeScript interface names

TLDR: If you’re writing TypeScript, you’re using intellisense, and it will yell at you if you try to use an interface as a variable. So there is no need anymore for naming conventions like IMyInterface or AMyAbstractClass.

When I first started using TypeScript in 2018 I saw a lot of people’s code prefixing interfaces with “I”:

interface ICar {
    color: string
    horsepower: number
}

function drive(car: ICar) { /*...*/ }

However, that is a convention borrowed from the past (other languages). It used to be mentioned in the TypeScript handbook to not prefix, but that generated more argument than resolution. The handbook code examples, however, do not use prefixes.

I believe modern dev environments have relieved us from the need to include type and contextual information into the names of many code things, not just interfaces.

Now we get proper type and context from things like intellisense and language servers, whether it’s inferred or strictly typed. This frees us to have names for things that can be more descriptive of the processes and data in question.

Control your magic strings in Firebase projects

Firebase’s real-time database JavaScript API makes it really easy to wind up with a ton of magic strings in your codebase. Since you access data via “refs” like:

firebase.database.ref(`customers/1`).update({ name: 'Mike' })

In that example, “customers/1” is a magic string that points to some data in the database. Magic strings are well known as something to avoid in software development. And here we are updating a database with one, yikes!

These can easily be managed via patterns. I’ve been abstracting these into their own modules in an “API-like” pattern. For example:

// api/customers.ts
import { protectedRef } from 'protected-ref'

export const rootRef = 'customers'
export const getCustomerRef = (customerID: string) => protectedRef(rootRef, customerID)
export const updateCustomer = (customerID: string, updateDocument: CustomerUpdateDocument) => getCustomerRef(customerID).update(updateDocument)

And then use it like:

import { updateCustomer } from '../api/customers'

updateCustomer('1', { name: 'Mike' })

Also protected-ref is a firebase package that manifested from this TIL: https://til.hashrocket.com/posts/hyrbwius3s-protect-yourself-against-firebase-database-refs

JavaScript "falsy" values are still values

Having good defaults doesn’t mean always having them in the function signature. I try to provide good defaults for my function args when possible. I like to provide them in the signature, too. However, I ran into a case where doing it in an object destructure would have caused a bug.

This function receives an object (fruit) and returns its price based on its selectedRate. It is expected that selectedRate can be '', null, or undefined, in addition to constants like "standardRate". In any case that the selected rate doesn’t exist, standardRate should be used:

function getFruitPrice({ selectedRate = 'standardRate', ...fruit }) {
  //...
}

The code above doesn’t satisfy this requirement. JavaScript considers empty strings and null to be values even though they are falsy. So, we can’t rely on setting the default this way. Instead a better implentation might look like this:

function getFruitPrice(fruit) {
  const rateToUse = fruit.selectedRate || 'standardRate'
  // ...
}

Protect Yourself Against Firebase Database Refs!

My worst nightmare is to lose production data, thank God for backups. But my nightmare came to life on a project’s Firebase database. This is a noSQL JSON-like document database offered as a service from Google Cloud Platform. You use it like this:

firebase.database.ref('/customers/1')

This is how you get the data at that path. Like a file system.

And when you want to make it dynamic:

firebase.database.ref(`/customers/${customerID}`)

Cool.

But, what if customerID is blank?

cue Nightmare Before Christmas soundtrack

funtion handleDelete(customerID) {
    firebase.database.ref(`/customers/${customerID}`)
    // ...delete()
}

If I ran that function in production with a blank customer ID, well then I just deleted all the customers.

Fear Not!

import protectedRef from './protectedRef.js'

const customerID = ''
protectedRef('customers', customerID) 
// => [ERROR] Bad ref! 1: ""

Use protection kids.

// protectedRef.js

import { sanitize } from './sanitize.js'

function protectedRefReducer(pathSoFar, pathPart, index) {
  const sanitizedPathPart = sanitize(pathPart)


  if (!sanitizedPathPart) {
    throw new Error(`Bad ref! ${index}: "${pathPart}"`)
  }

  return pathSoFar + '/' + sanitizedPathPart
}

export default function protectedRef(...parts) {
    if (!parts.length) {
    throw new Error('noop')
  }
  return firebase.database().ref(parts.reduce(protectedRefReducer, ''))
}

Use Typescript to help migrate/upgrade code

I am tasked with migrating a large javascript codebase from using the beta version of firebase-functions to the latest. Like most major upgrades, there are many API changes to deal with. Here’s an example cloud function:

Beta version with node 6:

exports.dbCreate = functions.database.ref('/path').onCreate((event) => {
  const createdData = event.data.val(); // data that was created
});

Latest version with node 10:

exports.dbCreate = functions.database.ref('/path').onCreate((snap, context) => {
  const createdData = snap.val(); // data that was created
});

The parameters changed for onCreate.

In the real codebase there are hundreds of cloud functions like this and they all have varying API changes to be made. With no code-mod in sight, I’m on my own to figure out an effecient way to upgrade. Enter Typescript.

After upgrading the dependencies to the latest versions I added a tsconfig:

{
  "compilerOptions": {
    "module": "commonjs",
    "outDir": "lib",
    "target": "es2017",
    "allowJs": true,
    "checkJs": true,
  },
  "include": ["src"]
}

The key is to enable checkJs so that the Typescript compiler reports errors in javascript files.

And running tsc --noEmit against the codebase provided me with a list of 200+ compiler errors pointing me to every change required.

How to setup VS Code for Ruby development

After some trial and error with the various extensions available for Ruby, I’ve found the following combination to work well:

Ruby for debugging, syntax highlighting and linting. I use these VS Code User Settings:

"ruby.useLanguageServer": true,
"ruby.lint": {
  "ruby": true
}

Solargraph for intellisense and autocomplete.

endwise for wisely adding end to code structures. (Inspired by tpope’s endwise.vim)

Prettier and plugin-ruby for formatting.

Prettier plugin support is on the way, but for now we have to do this

Bonus for Rails: PostgreSQL for writing queries within the editor.

How to write a render prop

Hi my name is Matt. This is how to write a dependecy-inverted React component using the render prop pattern. It’s useful when you want to encapsulate and share common logic without knowing how it will be used or what children it should render. The render prop pattern is the successor to higher-order components and solves HoC’s problems with naming collisions.

If you’re on the latest version of React (>= 16.8) you should probably use a custom hook instead.

function FullName({ children, firstName, lastName }) {
    const fullName = firstName + ' ' + lastName
    return children(fullName)
}

// Usage:
function App() {
  return (
    <FullName firstName="Thor" lastName="Odinson">
        {fullName => <h1>Hello {fullName}!</h1>}
    </FullName>
  )
}