diff --git a/assets/ceramic-letter-opener.jpg b/assets/ceramic-letter-opener.jpg new file mode 100644 index 0000000..70583be Binary files /dev/null and b/assets/ceramic-letter-opener.jpg differ diff --git a/blog/index.html b/blog/index.html index deb2350..81abe45 100644 --- a/blog/index.html +++ b/blog/index.html @@ -195,6 +195,17 @@ +

2024

+ + + + +

+ Opening Mail + 1/6 +

+ +

2023

diff --git a/blogpost-contexts/index.html b/blogpost-contexts/index.html index f12d83e..9793e32 100644 --- a/blogpost-contexts/index.html +++ b/blogpost-contexts/index.html @@ -238,6 +238,14 @@

Related posts:

+ + + + + + + + diff --git a/cdtmp/index.html b/cdtmp/index.html index 7a2340b..f3fc714 100644 --- a/cdtmp/index.html +++ b/cdtmp/index.html @@ -221,6 +221,14 @@

Related posts:

+ + + + + + + + diff --git a/copy-with-syntax/index.html b/copy-with-syntax/index.html index 99ec63c..f679857 100644 --- a/copy-with-syntax/index.html +++ b/copy-with-syntax/index.html @@ -220,6 +220,14 @@

Related posts:

+ + + + + + + + diff --git a/ctrl-r/index.html b/ctrl-r/index.html index 1f937b0..bb8e0fc 100644 --- a/ctrl-r/index.html +++ b/ctrl-r/index.html @@ -220,6 +220,14 @@

Related posts:

+ + + + + + + + diff --git a/e2e-tests/index.html b/e2e-tests/index.html index ea49a7e..77442c6 100644 --- a/e2e-tests/index.html +++ b/e2e-tests/index.html @@ -277,6 +277,14 @@

Related posts:

+ + + + + + + + diff --git a/feed.xml b/feed.xml index ca68ea7..7a96617 100644 --- a/feed.xml +++ b/feed.xml @@ -5,12 +5,38 @@ https://frantic.im/favicon.png Occasional posts on technology and stuff - 2023-11-22T18:31:03.008Z + 2024-01-06T17:12:37.992Z Alex Kotliarskyi + + https://frantic.im/opening-mail + Opening Mail + 2024-01-06T12:00:00+00:00 + + + First make the change easy, then make the easy change. + I never liked opening envelopes; they’re tricky and ripping them open is annoying. My letters would get stuck or tear with the envelope.

+

The mail just used to stack up, and I’d miss important stuff because of it.

+

But then I found this cool little gadget from Japan.

+ + + +

It’s well-made, affordable, and feels good to use. Plus, it’s safe.

+

The best part? It actually made me enjoy opening my mail.

+

After this experience, I started thinking differently about unpleasent tasks. Is there a tool or a service that add delight to mundane things?

+

I also started noticing when people do this subconiously. For example, most software engineers I know hate blogging, but they like building their own blog engine to make blogging more pleasant (I’m very guilty of this too).

+

Kent Beck nailed it: “for each desired change, make the change easy (warning: this may be hard), then make the easy change”.

+ + ]]>
+
+ https://frantic.im/whos-watching-the-watchdog Who's watching the watchdog? @@ -409,275 +435,4 @@ Things you can do: ]]> - - https://frantic.im/react-api-evolution - React API evolution - 2021-03-11T12:00:00+00:00 - - - From React.createClass to hooks: why React is at odds with JavaScript - React is ~8 years old. I remember the day when I saw the first demo — I was amazed at how genius yet how simple it was! I still carry that excitement to this day.

-

During this time React changed a bunch, but its core ideas have stayed the same. It’s still all about thinking about your UI code as a function of state, bringing state front and center, immutable data, one-directional data flows, composition over inheritance.

-

In this post I’ll share how the developer APIs have evolved, specifically we’ll talk about defining components and sharing common code between components.

- -

2013, React v0.3.0: React.createClass

-
/** @jsx React.DOM */
-var Timer = React.createClass({
-  propTypes: {
-    name: React.PropTypes.string.isRequired,
-  },
-  getInitialState: function () {
-    return { seconds: 0 };
-  },
-  tick: React.autoBind(function () {
-    this.setState({ seconds: this.state.seconds + 1 });
-  }),
-  componentDidMount: function () {
-    setInterval(this.tick, 1000);
-  },
-  render: function () {
-    return (
-      <div>
-        Hello, {this.props.name}! It's been {this.state.seconds} seconds
-      </div>
-    );
-  },
-});
-
-React.renderComponent(<Timer name="Alex" />, document.getElementById("main"));
-
-

Initeresting things to note here:

-
    -
  1. /** @jsx React.DOM */ was required for the JSXTransformer to convert XML-in-JS syntax (like <div>Hello</div>) to function calls (like React.DOM.div({}, 'Hello'))
  2. -
  3. React.createClass was used to create a component. I think in hindsight naming it Class and not Component was a big marketing mistake: with ES6 classes many people were pushing for React to adopt the “standard” way, although it had a lot of problems (more on that later).
  4. -
  5. In development, React performed props validation at runtime (Flow and TypeScript didn’t exist back then), and the PropTypes API allowed for pretty complex definitions with nested objects and arrays.
  6. -
  7. Initially, without React.autoBind the methods on the components had dynamically scoped this, which was pretty confusing: calling this.tick would result in something like “Can’t call setState of unndefined”. autoBind was doing something like fn.bind(this) to fix it on per-function basis, and eventually this behavior was moved directly into React.createClass.
  8. -
  9. React focused on a pure, functional, declarative approach to bulding UIs, but also had escape hatches that allowed programmers take imperative actions or talk to DOM when needed via lifecycle methods and refs.
  10. -
-

If you look carefully at the example above, you’ll notice that there’s a memory leak! We setInterval without clearInterval-ing it. To fix the problem we’d have to call clearInterval from componentWillUnmount, however that wasn’t obvious from the APIs and programmers had to watch out for patterns like this.

-

That was a common pitfall: managing resources and making sure parts that were not managed by React were in sync with the UI.

-

It was clear there’s a need for a way for the components to share behavior traits. Early versions of React shipped with a solution to this problem: mixins.

-

Mixins

-
/** @jsx React.DOM */
-
-var SetIntervalMixin = {
-  componentWillMount: function () {
-    this.intervals = [];
-  },
-  setInterval: function (callback, interval) {
-    this.intervals.push(setInterval(callback, interval));
-  },
-  componentWillUnmount: function () {
-    this.intervals.map(clearInterval);
-  },
-};
-
-var TickTock = React.createClass({
-  mixins: [SetIntervalMixin],
-
-  getInitialState: function () {
-    return { seconds: 0 };
-  },
-  componentDidMount: function () {
-    this.setInterval(this.tick, 1000);
-  },
-  tick: function () {
-    this.setState({ seconds: this.state.seconds + 1 });
-  },
-  render: function () {
-    return <p>It's been {this.state.seconds} seconds</p>;
-  },
-});
-
-

The code above fixes the memory leak and makes it easier to avoid this problem in the future: just include SetIntervalMixin and you are good to go!

-

Mixins fixed some problems, but intruduced others: implicit dependencies, name clashes and snowballing complexities. Read more on the official blog post (2016).

-

2015, React v0.13: class extends React.Component

-

The big feature of this release was ES6 class support:

-
-

JavaScript originally didn’t have a built-in class system. Every popular framework built their own, and so did we. This means that you have a learn slightly different semantics for each framework.

-
-
-

We figured that we’re not in the business of designing a class system. We just want to use whatever is the idiomatic JavaScript way of creating classes.

-
-
class Timer extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = {seconds: 0};
-  }
-  tick() {
-    this.setState({seconds: this.state.seconds + 1});
-  }
-  componentDidMount() {
-    setInterval(this.tick.bind(this), 1000);
-  }
-  render() {
-    return (
-      <div>
-        Hello, {this.props.name}! It's been {this.state.seconds} seconds
-      </div>
-    );
-  }
-});
-
-Counter.propTypes = {
-  name: React.PropTypes.string.isRequired,
-};
-
-

However, in my opinion ES6 classes didn’t fix the problem, but made it worse.

-

First, the benefits weren’t super valuable. React shipped Component and PureComponent to inherit from, inheriting other components was discouraged (in favor of composition).

-

Second, the semantics resulted in a bunch of ergonomics problems.

-

In the example above, if you forgot to do this.tick.bind(this), you’ll get the same “Can’t call setState of unndefined” as in pre-autoBind days. There were several popular ways to address this, none of them seemed ideal though:

- -
<button onClick={this.increment.bind(this)} />
-
- -
constructor(props) {
-  // ...
-  this.tick = this.tick.bind(this);
-}
-
- -
class Timer extends React.Component {
-  tick = () => {
-    // ...
-  };
-
-  componentDidMount() {
-    setInterval(this.tick, 1000);
-  }
-}
-
-

Higher-order components

-

As mixing were goin away, the developers needed to fill the gap: find a way to reuse common functionality across components.

-

HoC became a popular replacement for mixins. You can think of the pattern as writing a function that takes a component as its argument, and returns a new component that wraps it with some useful enhancement.

-

Here’s an example of HoC that does the same thing as the SetIntervalMixin from the earlier example:

-
function withTimer(Component) {
-  return class extends React.Component {
-    constructor(props) {
-      super(props);
-      this.intervals = [];
-    }
-    setInterval = (callback, interval) => {
-      this.intervals.push(setInterval(callback, interval));
-    };
-    componentWillUnmount() {
-      this.intervals.map(clearInterval);
-    }
-    render() {
-      // Render the original component with some additional props
-      return <Component {...this.props} setInterval={this.setInterval} />
-    }
-  }
-}
-
-class Timer extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = {seconds: 0};
-  }
-  tick() {
-    this.setState({seconds: this.state.seconds + 1});
-  }
-  componentDidMount() {
-    this.props.setInterval(this.tick.bind(this));
-  }
-  render() {
-    return (
-      <div>
-        Hello, {this.props.name}! It's been {this.state.seconds} seconds
-      </div>
-    );
-  }
-});
-
-

HoC promise is to use functional composition to solve the trait problem. But they do come with their own drawbacks too, especially around the ergonomics:

-
    -
  1. Creating and using them is verbose, it’s not uncommon to end up with wrappers on top of wrappers, e.g. withTranslations(withTimer(connect()(Timer))).
  2. -
  3. This indirection breaks refs and makes writing pure components harder, unless implemented carefully.
  4. -
  5. Devtools show very deep wrapped component hierarchies that hurt readability:
  6. -
-

-

Render props

-

React community kept looking for better ways to reuse logic in components and for some time a pattern called “render props” gained a bunch of popularity. I’m not going to dive into these dark ages, but the idea was similar to HoC.

-

2019, React v16.8: Hooks

-

Around the time the release with ES6 was published, the React team made it possible to define components as simple functions, aka “stateless functional components”:

-
function Timer(props) {
-  return <div>Hello, {props.name}!</div>;
-}
-
-ReactDOM.render(<Timer name="Alex" />, document.getElementById("main"));
-
-

This was very popular: simple, concise, idiomatic. However, how do you get access to state or lifecycle methods?

-

After a bunch of prototyping and explorations, the React team presented the way – hooks.

-
function Timer(props) {
-  const [seconds, setSeconds] = useState(0);
-
-  useEffect(() => {
-    const interval = setInterval(() => setSeconds((s) => s + 1), 1000);
-    return () => {
-      clearInterval(interval);
-    };
-  }, []);
-
-  return (
-    <div>
-      Hello, {props.name}! It's been {seconds} seconds
-    </div>
-  );
-}
-
-

Programmers familiar with algebraic effects saw the striking similarities.

-

Notably, the mental model of hooks shifted from “lifecycle methods” to “sync things outside React’s control with UI”.

-

For example, useEffect is built in a way that makes it easy to colocate resource acquisition and release, making memory leaks much easier to avoid. The second argument to useEffect is a list of dependencies, if any of them change between calls to the same useEffect, React will clean up the previous one and will recreate a new one. Getting this right with componentDidMount / componentWillReceiveProps / componentWillUnmount was hard.

-

Hooks have solved the problem of sharing common functionality across components in a very elegant, composable ways:

-
function useInterval(callback, ms) {
-  useEffect(() => {
-    const interval = setInterval(callback, ms);
-    return () => {
-      clearInterval(interval);
-    };
-  }, [callback, ms]);
-}
-
-function Timer(props) {
-  const [seconds, setSeconds] = useState(0);
-  useInterval(() => setSeconds((s) => s + 1), 1000);
-
-  return (
-    <div>
-      Hello, {props.name}! It's been {seconds} seconds
-    </div>
-  );
-}
-
-

But hooks were not without problems either: in the example above there’s a subtle problem with the callback we pass to useInterval: since it’s a new referance every time (in JS, () => {} !== () => {}) we end up re-creating interval every render. The solution looks like this:

-
function Timer(props) {
-  const [seconds, setSeconds] = useState(0);
-  const increment = useCallback(() => setSeconds((s) => s + 1), []);
-  useInterval(increment, 1000);
-
-  // ...
-}
-
-

Compared to React.Component and mixins, React Hooks traded this and related class gotchas for JS scope gotchas. I think it was a good trade to make.

-

Conclusions

-
    -
  1. React did an awesome job keeping the API surface very small. Watching the documentation across all these years felt like the team is actively removing things that are non-essential.
  2. -
  3. React evolved in a steady, backwards-compatible way. You can still use React.createClass APIs via a package, if you want to. Facebook code written in 2013 still works fine (after applying minor codemods).
  4. -
  5. React is at odds with JavaScript: from JSX syntax, ES6 class method bindings gotchas to reinvention of algebraic effects.
  6. -
- - ]]>
-
- \ No newline at end of file diff --git a/figma/og_opening_mail.png b/figma/og_opening_mail.png new file mode 100644 index 0000000..88d2eab Binary files /dev/null and b/figma/og_opening_mail.png differ diff --git a/good-errors-leave-trace/index.html b/good-errors-leave-trace/index.html index 5ee3e24..c1ff696 100644 --- a/good-errors-leave-trace/index.html +++ b/good-errors-leave-trace/index.html @@ -274,6 +274,14 @@

Related posts:

+ + + + + + + + diff --git a/hacker-gifts/index.html b/hacker-gifts/index.html index cf9e9fb..147f975 100644 --- a/hacker-gifts/index.html +++ b/hacker-gifts/index.html @@ -265,6 +265,14 @@

Related posts:

+ + + + + + + + diff --git a/hello-world/index.html b/hello-world/index.html index 40d81b5..b253d83 100644 --- a/hello-world/index.html +++ b/hello-world/index.html @@ -214,6 +214,14 @@

Related posts:

+ + + + + + + + diff --git a/how-not-to-flux-loops/index.html b/how-not-to-flux-loops/index.html index 676db3e..3c84d80 100644 --- a/how-not-to-flux-loops/index.html +++ b/how-not-to-flux-loops/index.html @@ -258,6 +258,14 @@

Related posts:

+ + + + + + + + diff --git a/how-not-to-flux-set-actions/index.html b/how-not-to-flux-set-actions/index.html index 097cac2..62fd323 100644 --- a/how-not-to-flux-set-actions/index.html +++ b/how-not-to-flux-set-actions/index.html @@ -291,6 +291,14 @@

Related posts:

+ + + + + + + + diff --git a/how-to-convince-your-boss-to-use-react-native/index.html b/how-to-convince-your-boss-to-use-react-native/index.html index 600d1db..639f497 100644 --- a/how-to-convince-your-boss-to-use-react-native/index.html +++ b/how-to-convince-your-boss-to-use-react-native/index.html @@ -261,6 +261,14 @@

Related posts:

+ + + + + + + + diff --git a/keynote/index.html b/keynote/index.html index 3ffe5f7..82bf6c0 100644 --- a/keynote/index.html +++ b/keynote/index.html @@ -256,6 +256,14 @@

Related posts:

+ + + + + + + + diff --git a/macos-app-shortcuts/index.html b/macos-app-shortcuts/index.html index 108cf47..0e42044 100644 --- a/macos-app-shortcuts/index.html +++ b/macos-app-shortcuts/index.html @@ -232,6 +232,14 @@

Related posts:

+ + + + + + + + diff --git a/no-constraints-no-fun/index.html b/no-constraints-no-fun/index.html index 8d90c55..127775b 100644 --- a/no-constraints-no-fun/index.html +++ b/no-constraints-no-fun/index.html @@ -211,6 +211,14 @@

Related posts:

+ + + + + + + + diff --git a/notify-on-completion/index.html b/notify-on-completion/index.html index 38c8451..0ff899c 100644 --- a/notify-on-completion/index.html +++ b/notify-on-completion/index.html @@ -252,6 +252,14 @@

Related posts:

+ + + + + + + + diff --git a/octave/index.html b/octave/index.html index b14b553..2e3cb00 100644 --- a/octave/index.html +++ b/octave/index.html @@ -233,6 +233,14 @@

Related posts:

+ + + + + + + + diff --git a/onityper/index.html b/onityper/index.html index 27d8a5f..3069b7b 100644 --- a/onityper/index.html +++ b/onityper/index.html @@ -295,6 +295,14 @@

Related posts:

+ + + + + + + + diff --git a/opening-mail/index.html b/opening-mail/index.html new file mode 100644 index 0000000..7aed406 --- /dev/null +++ b/opening-mail/index.html @@ -0,0 +1,237 @@ + + + + + + + Opening Mail / frantic.im + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +

Opening Mail

+
+

I never liked opening envelopes; they’re tricky and ripping them open is annoying. My letters would get stuck or tear with the envelope.

+

The mail just used to stack up, and I’d miss important stuff because of it.

+

But then I found this cool little gadget from Japan.

+ + + +

It’s well-made, affordable, and feels good to use. Plus, it’s safe.

+

The best part? It actually made me enjoy opening my mail.

+

After this experience, I started thinking differently about unpleasent tasks. Is there a tool or a service that add delight to mundane things?

+

I also started noticing when people do this subconiously. For example, most software engineers I know hate blogging, but they like building their own blog engine to make blogging more pleasant (I’m very guilty of this too).

+

Kent Beck nailed it: “for each desired change, make the change easy (warning: this may be hard), then make the easy change”.

+ + + + + + + + + +
+
+

Hello! This text lives here to convince you to subscribe. If you are reading this, consider clicking that subscribe button for more details.

+

I write about programming, software design and side projects Subscribe

+
+
+ +
+ + + + + diff --git a/plotting-ideas/index.html b/plotting-ideas/index.html index d941e64..b83beb7 100644 --- a/plotting-ideas/index.html +++ b/plotting-ideas/index.html @@ -236,6 +236,14 @@

Related posts:

+ + + + + + + + diff --git a/react-and-javascript-in-5-min/index.html b/react-and-javascript-in-5-min/index.html index 776e97a..0c6d5a4 100644 --- a/react-and-javascript-in-5-min/index.html +++ b/react-and-javascript-in-5-min/index.html @@ -404,6 +404,14 @@

Related posts:

+ + + + + + + + diff --git a/react-api-evolution/index.html b/react-api-evolution/index.html index 18b2354..3d68a2d 100644 --- a/react-api-evolution/index.html +++ b/react-api-evolution/index.html @@ -467,6 +467,14 @@

Related posts:

+ + + + + + + + diff --git a/react-conf-2018/index.html b/react-conf-2018/index.html index 96db4cc..402bc82 100644 --- a/react-conf-2018/index.html +++ b/react-conf-2018/index.html @@ -311,6 +311,14 @@

Related posts:

+ + + + + + + + diff --git a/replacing-jekyll/index.html b/replacing-jekyll/index.html index 30f7e44..c4ede4f 100644 --- a/replacing-jekyll/index.html +++ b/replacing-jekyll/index.html @@ -247,6 +247,14 @@

Related posts:

+ + + + + + + + diff --git a/side-projects-are-hard/index.html b/side-projects-are-hard/index.html index 7319eed..26b43eb 100644 --- a/side-projects-are-hard/index.html +++ b/side-projects-are-hard/index.html @@ -233,6 +233,14 @@

Related posts:

+ + + + + + + + diff --git a/test-plan/index.html b/test-plan/index.html index 372074a..9eb2d59 100644 --- a/test-plan/index.html +++ b/test-plan/index.html @@ -224,6 +224,14 @@

Related posts:

+ + + + + + + + diff --git a/the-first-react-native-app/index.html b/the-first-react-native-app/index.html index c1ef951..bf00ad8 100644 --- a/the-first-react-native-app/index.html +++ b/the-first-react-native-app/index.html @@ -248,6 +248,14 @@

Related posts:

+ + + + + + + + diff --git a/using-redux-with-flow/index.html b/using-redux-with-flow/index.html index 7d15bf2..c47ae66 100644 --- a/using-redux-with-flow/index.html +++ b/using-redux-with-flow/index.html @@ -308,6 +308,14 @@

Related posts:

+ + + + + + + +