A Work in Progress

developer's blog by 
Credits

Notes on Flow 0.101

June 25, 2019

The upgrade to Flow v0.101 was surprisingly quiet in our code base. But the release notes contain some interesting reads which I’ll summarize as: a looming default on inexact object, retapping on React.memo and React.lazy, and LSP and performance improvements.

Here are some notes I jotted down as I learn about the changes. I’ve started building this Flow Notes repo too.

Note This is not a complete list of the changes. There are many more commits and especially around around LSP and performance optimizations that I have not written about.


We released a new implicit-inexact-object lint to detect when an inexact object is used without explicitly adding ... to the end of the props list.

This looks like a scary future causal of unhappiness because after then objects will be exact by default. And reading On the Roadmap: Exact Objects by Default, it seems that this has been plotted for a while and may land anytime. Although, as you may read in the related commit, this linting rule is still an opt-in. Winter hasn’t come yet.


React.memo and React.lazy now both allow you to specify an instance type via React.AbstractComponent.

Previously React.memo and React.lazy used React.ComponentType which became stricter after v0.100. This recent change that landed on v0.101 changed React.memo and React.lazy to use React.AbstractComponent internally. They now both take an extra type parameter for the instance. It also means that we can now seamlessly pass the returns of React.forwardRef to React.memo and React.lazy. Check out the following test files for demos:


React.memo

React.memo, with memo for memoization, is used on functional components that are supposed to render the same result given the same props.

The signature of React.memo is as follows:

declare export function memo<Config, Instance = mixed>(
  component: React$AbstractComponent<Config, Instance>,
  equal?: (Config, Config) => boolean
): React$AbstractComponent<Config, Instance>

And we can use it this way:

// @flow
const React = require('react')

type Props = {| cornStyle: 'spiraling' | 'colorful' |}
function Unicorn(x: Props) {
  return null
}
const MemoUnicorn = React.memo<Props>(Unicorn)

const memo_unicorn_ok = () => <MemoUnicorn cornStyle="spiraling" /> // ok
const memo_unicorn_error = () => <MemoUnicorn corn="spiraling" /> // error

We may optionally provide an equal method as a performance optimization. React.memo provides the type information for the method with the same props type we supplied, and we don’t need any extra type annotations for it. And since we did provide a Props type parameter to React.memo, Flow is able to catch errors on the equal method:

const memo_unicorn_with_equal_ok =
      React.memo<Props>(Unicorn, (props1, props2) => props1.cornStyle === props2.cornStyle); // ok

const memo_unicorn_with_equal_error = 
      React.memo<Props>(Unicorn, (props1, props2) => props1.corn === props2.corn); // error

And we can pass the returns of React.forwardRef to React.memo:

const { useImperativeHandle } = React

function Demo(props, ref) {
  useImperativeHandle(ref, () => ({
    moo(x: string) {},
  }))
  return null
}

const Memo = React.memo(React.forwardRef(Demo))

function App() {
  // Error below: moo expects a string, given a number
  return <Memo ref={ref => ref && ref.moo(0)} />
}

React.lazy

React.lazy currently lets us render a dynamic import as a regular component. The signature of React.lazy tells us that it takes an async function which resolves to a module export, which in turn contains a default field:

declare export function lazy<P>(
  component: () => Promise<{ default: React$ComponentType<P>, ... }>
): React$ComponentType<P>

Here’s how we may annotate a React.lazy loaded funcitonal component:

//@flow
const React = require('react')

type Props = {| cornStyle: 'spiraling' | 'colorful' |}

function Unicorn(x: Props) {
  return null
}

const LazyFunctionComponent = React.lazy(() =>
  Promise.resolve({ default: Unicorn })
)

const lazy_unicorn_ok = () => <LazyUnicorn cornStyle="spiraling" /> // ok
const lazy_unicorn_error = () => <LazyUnicorn corn="spiraling" /> // error

Likewise, we can pass the returns of React.forwardRef to React.lazy:

const { useImperativeHandle } = React

function Demo(props, ref) {
  useImperativeHandle(ref, () => ({
    moo(x: string) {},
  }))
  return null
}

const Lazy = React.lazy(async () => ({
  default: React.forwardRef(Demo),
}))

function App() {
  // Error below: moo expects a string, given a number
  return (
    <React.Suspense fallback="Loading...">
      <Lazy ref={ref => ref && ref.moo(0)} />;
    </React.Suspense>
  )
}

✌️ Till next time

Recently I’ve started organizing my notes to a new GitHub repo: Flow Notes. Mainly because:

  • These notes work better when organized by topics instead of as stream of versioned updates
  • The repo can be a home base where people can check back for Flow usages and questions
  • It’s easier to collaborate

This is also inspired by React TypeScript Cheatsheet maintained by @swyx and a few others. Our brother faction TypeScript has much better community support than Flow. They have books, a number of cheatsheets and guides, active channels such as r/typescript, etc. Whereas none of Flow’s communities are effectively active, this includes r/flowtype, stack overflow, Reactiflux, searching flowtype under r/react, etc.

As I learn more about Flow, I realize that much of the high quality documentation is in Flow’s and Flow Typed’s code base. Even the Flow team constantly encourages people to read the type definitions from the source code directly, and references people to those places. While it is very true and the source code is my major source of understanding, checking the source code for docs is probably not the default behavior by normal users. People get frustrated when their main goal is to implement their features and not to become expert in Flow. Furthermore, while the code is indeed very self-documented, the code base contains much more information than what is needed by normal users. Somehow that reminds me of legal documents ¯\_(ツ)_/¯

That said, I believe there is more work that needs done in its documentation. And I’d like to try if herding my notes in GitHub is an effective idea. So do check it out if it sounds like something that may be helpful, let me know what is needed and in what format, and it’d be even greater if you’d like to join this party of putting up organized notes for Flow.

References

Resources

Commits

Examples

React

Subscribe to my newsletter