Cory House Profile picture
I talk code. Courses: https://t.co/D5emROQa4J YouTube: https://t.co/pWIz4BMXsc Consulting: https://t.co/Qfp4Tfp3jf ⚛️
2 subscribers
Sep 25, 2023 4 tweets 2 min read
Problem: You want to assure your web app isn’t logging messages to the console, and enforce it on CI.

Solution: Use Playwright.

With these 3 lines, I can fail a test if anything is written to the console. I can also be more specific and only fail if a console error occurs. // To keep the console clean, throw an error to fail  // the test immediately if a console message occurs. page.on("console", (message) => {   // Optionally, can check for only errors like this:    // if (message.type() === "error") {   throw new Error(message.text()); }); To make this easy to apply to every test, I created my own page goto function that also applies this console config. import { Page } from "@playwright/test";  // To keep the console clean, throw an error to fail the test immediately if a console message occurs. export function throwOnConsole(page: Page) {   page.on("console", (message) => {     // Optionally, can check for only errors like this: if (message.type() === "error") {     throw new Error(message.text());   }); }  // Load a page and configure the console to throw an error if a console message occurs. export async function goTo(page: Page, path: string) {   await page.goto(path);   throwOnConsole(page); }
Jun 15, 2023 5 tweets 2 min read
Problem:
When you deploy a new version of your SPA, users with a tab open will keep using the old SPA code.

Solution:
1. Specify the app version in each HTTP call from the UI via a x-app-version header.

2. Validate the x-app-version header on the API server. If x-app-version… twitter.com/i/web/status/1… If you prefer, you can use HTTP 422 instead. It's more specific than a 400, and feels like a good fit for this use case.

And the header name can be whatever you like. Anything that starts with "x-" is a fine choice.

developer.mozilla.org/en-US/docs/Web…
Jun 9, 2023 5 tweets 2 min read
A bug I see in nearly every web app: If someone saves a stale record, it overwrites the newer DB record. 😬

Solution:
1. Provide 'updated' time with each GET

2. Require 'updated' when sending a POST/PUT

3. If 'updated' doesn't match the DB, return an HTTP 409 (conflict) Image When the API returns an error status because the record was stale, tell the user "The record you tried to save has been changed by someone else."

Then, 4 options:
1. Tell the user to reload and try again (Easiest and safest, but annoying for the user)

2. Provide a "Save anyway"… twitter.com/i/web/status/1…
Apr 26, 2023 4 tweets 1 min read
Hard truth: Some devs are WAY more productive.

As a consultant, I work with dozens of teams. I see massive differences in developer output.

Here's an example on the same team:
Dev 1: 45 PRs/month
Dev 2: 1 PR/month

I did code reviews. Dev 1 had higher code quality too. And yes, stats can be gamed. So, I don't recommend managing based on stats. But when stats are wildly lopsided, there's often a root cause worth exploring.

In this case, the dev who completed 45 PRs had many PRs that were more complex than the 1 PR merged by the other dev.
Apr 1, 2023 4 tweets 1 min read
Continuous integration:
✅ No PRs
✅ No branches
✅ No gating code reviews
✅ Commit directly into trunk

Frequent Integration:
✅ Small PRs
✅ Simple, short-lived feature branches
✅ Code review before merge
✅ Merge branches ~daily

Frequent integration is often good enough. For many teams, "frequent integration" is the sweet spot.

Doing code reviews before merge assures reviews actually happen. (you can pair, but few devs do consistently, and outside reviews are useful when pairing too)

Integrating work approximately daily is frequent enough.
Mar 23, 2023 5 tweets 2 min read
TypeScript tip: Try to avoid optional fields.

They cause two problems:
1. They reduce type safety.
2. There's no type-safe way to enforce when the field should be populated.

Solution: Often optional fields can be eliminated by declaring separate and more specific types. // Optional field 👎 interface Car {   model: string;   as Principle: The more information we can provide TypeScript, the more it can help us.
Mar 22, 2023 4 tweets 1 min read
I typically run the app when reviewing a PR.

Here's why:
✅Helps me see the user experience.

✅Seeing the UX helps me read the code, because it helps me understand what the code does, and why.

✅It gives me ideas for checking the code for edge cases.

✅ Viewing and… twitter.com/i/web/status/1… Concern: "Ain't nobody got time for that".

Reality: Thorough reviews save time. Reviews avoid rework, catch issues early, enforce consistency, and so on.

And ideally I simply run the automated in-browser tests provided with the PR. That takes very little time.
Feb 11, 2023 4 tweets 1 min read
Software death spiral:
“We don’t have automated tests, so we need many environments with slow, manual checks in each.” 😬

Virtuous cycle:
“We have comprehensive tests, so we deploy straight to prod automatically when the CI build passes. We deploy multiple times a day.”🔥 The lack of automated tests means we have to batch work into releases. Batching work means we need multiple environments, with manual testing in each. And we have to plan releases.

Comprehensive automated tests and daily automated deploys eliminate all this overhead. 👍
Feb 9, 2023 6 tweets 1 min read
A developer who immediately approves PRs is a liability. Why? Because they eliminate an opportunity for a *real* review.

A fake reviewer glances at the PR and quickly approves.

A real reviewer reads/runs the code, asks questions, considers alternatives, and makes suggestions. Many replies boil down to “ain’t got time for that”

I frequently work with clients who want to improve their team’s velocity.

The common thread? The teams are doing few meaningful code reviews.

Glossing over reviews doesn’t save time. It steals time from tomorrow.
Jan 30, 2023 4 tweets 1 min read
The closer developers are to end users, the better.

Why? End user feedback increases empathy, motivates developers, shortens the feedback loop, and enables rapid, cheap experimentation.

Developers working with end users is the epitome of agile. The first line of the agile manifesto:

“Individuals and interactions over processes and tools”

Developers iterating directly with end users is a wonderful example of agile in action. 👍
Jan 19, 2023 9 tweets 2 min read
15 reasons I enjoy @tailwindcss:

1. I don’t have to spend time naming classes.

2. I can jump into a project that uses it immediately.

3. The final CSS is automatically as small as possible. No unused styles are shipped to prod.

4. Fantastic docs. 5. I don’t have to jump between files, or keep a CSS and HTML file’s classes in sync.

6. I get autocomplete for Tailwind styles in my editor (Tailwind CSS IntelliSense extension).

7. I can quickly build attractive UIs without a designer.
Jan 10, 2023 6 tweets 2 min read
Pre-commit hooks are a waste of time.

Why? Because they enforce standards too early. Pre-commit hooks make us wait for a quality check on every commit.👎

The code shouldn't need to be "right" to save my changes.

The code only needs to be right before merge.

Solution? Use CI. I should be able to *instantly* commit anything.

This CI server assures I can’t merge broken code. And the CI server avoids wasting my time waiting for premature quality checks on every commit.

The CI server assures the code is worthy before I can merge it. 👍
Dec 28, 2022 5 tweets 1 min read
13 ways to be a lousy developer:

- Don't volunteer to help others.

- Don’t try to get better or learn.

- Don't test code before opening a PR.

- Rubber stamp PRs, or don’t review them at all.

- Don't be proactive. Only act when told to do so.

1/5 👇 - Write hacky, buggy, hard to understand code.

- Often fail to finish work. Give up if it’s hard.

- Don't use personal judgement. Only do precisely what you're told.

- Be hard to contact during the day and frequently unavailable.

2/5
Dec 22, 2022 10 tweets 2 min read
3 seemingly unrelated items that are deeply connected:

1. Career
2. Health
3. Finances

If I only optimize one or two of these, I limit my potential.

Here’s why… Optimizing my career improves my finances.

Optimizing my career improves my health too - Improving my effectiveness at work reduces my stress.

If my job is impacting my health, getting better at it increases my bargaining power (work flexible hours, remote, so I can workout).
Dec 18, 2022 4 tweets 1 min read
A unit test shouldn’t:

🚫 Call a DB
🚫 Run a browser
🚫 Make an HTTP call
🚫 Rely on stuff on other machines

A unit test should pass without a network connection.

There’s nothing wrong with doing such things in a test. But if a test does these things, it’s not a unit test. One more thing:

The definition of "unit test" matters.

Why? It gives us a clear goal.

A unit test's goal: Test in isolation, quickly.

Other types of tests like integration, end-to-end, performance, etc, are useful too. But they have different goals.
Dec 1, 2022 5 tweets 2 min read
Yesterday I shared why I'm switching from Cypress to Playwright.

But Cypress is solid too. Here are 4 Cypress benefits:

1. More concise syntax. In Cypress, I don’t have to say “await”, “expect”, or “exact” constantly. In Playwright, I must use these 3 keywords a lot.

1/5 👇 Image 2. Built-in watch mode. Cypress' test runner stays open and re-runs a single test on save. 😍 I really miss this feature in Playwright.

Playwright requires hacks to do something similar: github.com/microsoft/play…
Nov 30, 2022 7 tweets 3 min read
I’m a big Cypress fan, so I’m shocked to say this: I just switched to @playwrightweb.

Here are 16 reasons I switched:

1. WAY Faster. ~2X faster with 1 core. ~6x faster with multiple cores (uses multiple workers)

2. Tests multiple browsers in parallel.

1/5

Comparison:👇 ImageImage 3. Uses browser automation APIs, so it's efficient.

4. Simple config, with thoughtful defaults.

5. No globals.

6. Uses plain async/await.

7. Elegantly tests across domains. No extra complexity required. (see pic in first tweet)

8. Supports TypeScript out of the box.

2/5 👇
Oct 31, 2022 5 tweets 1 min read
⚛️10 reasons to prefer useReducer over useState:

1.  Separates reads from writes.

2. Avoids useState pitfalls. You should use functional setState to safely update state using existing state. With useReducer, you can't mess this up.

3. Easy to test. It's a pure function.

#react 4. Typically simplifies useEffect by removing unnecessary dependencies

5. Centralizes state writes, which is often easier to understand

6. Elegantly handles related state (Note you can also use objects with useState to compose data that changes together)
Sep 30, 2022 4 tweets 1 min read
Tailwind feels as logical as Prettier.

Prettier
✅ Save time by auto-formatting code
✅ Eliminate decision fatigue
✅ Easily switch projects
✅ Code faster

Tailwind
✅ Save time by using predefined styles
✅ Eliminate decision fatigue
✅ Easily switch projects
✅ Code faster Society moves forward the more decisions we can take for granted.

With Prettier, I don't think about formatting. I don't check it in code reviews. It's a solved problem.

With Tailwind, I don't think about class names. I don't check them in code reviews. It's a solved problem.
Sep 22, 2022 5 tweets 1 min read
React: The Good Parts™

Core API:
1. Function components
2. JSX
3. useState
4. useReducer
5. useRef
6. lazy
7. Suspense

Ecosystem:
1. TypeScript
2. Vite/Next/Remix
3. React Router
4. React Query
5. react-error-boundary
6. clsx
7. testing-library

#react What I mean by “The Good Parts”:

React’s API has grown. And the ecosystem is massive. So it's hard to choose.

"The Good Parts" are React’s best features and most trusted ecosystem projects.

The other stuff isn’t “bad”. But "The Good Parts” are often all I need.
Sep 7, 2022 8 tweets 1 min read
I ❤️ React. But, it’s not perfect.

There are features you should avoid.

Here’s “React: The Bad Parts”

👇 Class components - I can’t think of a good reason to use class components anymore.

Use functions. The API is simpler

Use react-error-boundary so you don’t need to use classes for boundaries.