Type-Checking React and Redux (+Thunk) with Flow - Part 1

Flow is a static type-checker for JavaScript. I love it because it detects errors in my code even before I run it and provides awesome autocomplete experience.

Now I’m obsessed with always type-checking my code, and I’ve come across many roadblocks when doing that, because there aren’t always examples available. I’ll share what I’ve learned during my usage of Flow with React, and hopefully it’ll improve your experience with Flow with React code.

meme type check all the things

I’m assuming you’ve already followed the guide on flow’s website and know about basic usage. If you haven't, go read it and then come back to this article.

Let’s get started, shall we?

Stateless Function Components

Type-checking stateless function components is just like type-checking plain functions. A stateless function component takes the props object as its argument. So we just have to specify the type for it.

For example:

import * as React from 'react';
type Props = {|
title: string,
body: string,
|}
export default function Article({ title, body }: Props) {
return (
<article>
<h1>{title}</h1>
<p>{body}</p>
</article>
);
}

Simple, right? Now when you don’t pass the correct props, flow warns:

Flow warning

Nice.

You can also type-check the children prop. For example:

type Props = {|
title: string,
children: React.Node,
|}
view raw type Props hosted with ❤ by GitHub

For normal components accepting children, this should be fine. However, you may add a more specific type definition if you need.

If you want to use React’s default props, flow doesn’t understand them with function components, so you need to use the default parameters syntax. For example:

import * as React from 'react';
type Props = {|
count?: number,
|}
export default function Counter({ count = 0 }: Props) {
return <span>{count}</span>;
}

Also note the | character at the beginning and end of the type definition. This is the exact object type syntax. Using it will make sure that you don’t pass any extra props to the component that it doesn’t receive. You could leave it out, but it is nice to have because it can catch typos etc.

Class Components

A class component can have props and state, so we need to specify types for a bit more.

import * as React from 'react';
type Props = {|
title: string,
body: string,
time: number,
track: () => Promise<void>,
|}
type State = {|
tracked: boolean,
|}
export default class Article extends React.Component<Props, State> {
static defaultProps = {
time: Date.now(),
};
state: State = {
tracked: false,
};
componentDidMount() {
this.props.track().then(() =>
this.setState({ tracked: true })
);
}
render() {
const { title, body, time } = this.props;
return (
<article>
<h1>{title}</h1>
<time dateTime={time}>
{(new Date(time)).toUTCString()}
</time>
<p>{body}</p>
</article>
);
}
}

Now, when we forget to pass a prop, Flow warns:

Flow warning

This is a bit confusing since it warns in the component we’re passing the props to rather than where we’re passing the props, but if you run Flow from CLI or have CI setup in your repo, you can catch this error.

Note that here we’re not passing a time prop, but since it’s in the defaultProps, Flow understands that and doesn’t warn.

Since we’ve declared the type of state here, if you forget to declare a property, declare wrong type or do setState with wrong type, flow will warn you.

Flow warning - wrong type

Note that the caveat mentioned about children props above still apply here.

If your component doesn’t accept props, you can define it as just {}(empty object). If it doesn’t have state, you can leave it out:

<strong>export</strong> <strong>default</strong> <strong>class</strong> Article <strong>extends</strong> React.Component<{}>
view raw React.Component hosted with ❤ by GitHub

Transferring Props

Sometimes we have components which consume one or more props, and transfer rest of the props to another component using the spread operator. The intermediate component could be a simple wrapper which does something like data fetching and passes a prop to underlying component, or just changes some behaviour.

For example:

function DashboardContainer({ isAdmin, ...rest }) {
if (isAdmin) {
return <Dashboard {...rest} />;
}
return <NotAllowedPage />;
}

It is not clear how to avoid having to declare all the prop types again in the intermediate component. But fear not, it’s possible.

Flow has a feature called generics, which lets you declare an abstract type without having to know what’s inside. Check the docs on the Flow website to know more about it. Flow also has another feature where you can specify the type as * to let flow infer the type. We can combine both to achieve what we want.

For example, say we have an Article component, and a wrapper IntermediateArticle component, and we want to avoid declaring all the prop types again in IntermediateArticle component. We can do something like this:

type IntermediateArticleProps<T> = T & {
title: string,
topic: 'programming' | 'science',
}
function IntermediateArticle<T: *>({ title, topic, ...rest }: IntermediateArticleProps<T>) {
return <Article title={`${topic} | ${title}`} {...rest} />;
}
function App() {
return (
<IntermediateArticle
title='ES2015'
body='Super cool stuff'
topic='programming'
/>
);
}

Notice the line: function IntermediateArticle<T: *>(props: IntermediateArticleProps<T>)

Here, we’re declaring an abstract type, and by saying T: *, we’re telling flow to infer it. So Flow basically infers the rest of the props and we don’t have to declare the prop types of Article again.

Flow still gives us error when we forget to pass some props:

code flow warning props

For class components, we need to do some modifications:

type IntermediateArticleProps = {
title: string,
topic: 'programming' | 'science',
}
class IntermediateArticle<T> extends React.Component<void, IntermediateArticleProps & T, void> {
props: IntermediateArticleProps & T;
render() {
const { title, topic, ...rest } = this.props;
return <Article title={`${topic} | ${title}`} {...rest} />;
}
}

And flow will warn us when we forget to pass props:

Flow warning props pass 2

Neat.

Higher Order Components

HOCs are common pattern in React where we have a function which takes a React component, adds some functionality, and then returns an enhanced component.

Sometimes we inject a new prop to the wrapped component. To type-check such an HOC, we can annotate it as follows:

type InjectedProps = {
trackingID: string,
}
function withTrackingID<Props: {}>(
BaseComponent: React.ComponentType<Props>): React.ComponentType<$Diff<Props, InjectedProps>> {
return class extends React.Component {
static displayName = `withTrackingID(${BaseComponent.displayName || BaseComponent.name})`;
render() {
return (
<BaseComponent
{...this.props}
trackingID='someID'
/>
);
}
};
}

Here we take advantage of abstract types as well as generics.

Static type-checking in JavaScript is pretty new, and there’s so much to explore and improve. Even though flow is pretty good at inferring types, it’s not always obvious how to type-check your code. I hope this article was a good starting point for you to start introducing flow to your React codebase. If you have any tips or improvements, please let me know.This is the first part of a two part series and only covers React. If you want to read about type-checking Redux, see part 2.

Insights

Learn more about Testing

How AI Agents Can Cut Manual QA by 30% — cover

Oct 30Article

How AI Agents Can Cut Manual QA by 30%

Explore an agentic QA workflow targeting 30% less manual effort across mobile, web, and TV, with test selection, failure triage, and validation.

Give QA Agents the Controls They Need With Appduct — cover

Oct 20Article

Give QA Agents the Controls They Need With Appduct

Use Appduct to give AI QA agents access to app functions for setting test conditions, triggering events, and checking results with UI automation.

cover

Oct 13Article

Agentic Mobile Testing With e2e and agent-device

See how e2e uses agent-device for AI-driven iOS and Android tests, combines exact assertions with agent actions, and replays cached steps without model calls.

Who Tests AI-Written Code? Inside TesterArmy With Szymon Rybczak — cover

Oct 12Podcast

Who Tests AI-Written Code? Inside TesterArmy With Szymon Rybczak

Szymon Rybczak explains how TesterArmy tests web and mobile apps with AI agents, creates test plans for pull requests, and fits into QA workflows.

cover

Aug 4Article

Automating Mobile QA With Cloud Agents

Code review and green CI checks do not prove a mobile app still works. Learn how cloud agents, agent-device, and runtime evidence can verify app behavior before merge.

cover

Jul 21Webinar

Behavior Review for AI-Native Delivery

AI can increase code output faster than teams can verify the real app. See why behavior review matters before merge.

cover

Jul 6Article

Introducing Sniffler: Run only the E2E tests your changes can affect

Sniffler maps changed files to affected E2E tests, so teams can avoid running the full suite on every pull request.

cover

Jun 17Talk

Automating Mobile QA with Cloud Agents

Michał Pierzchała will present Automating Mobile QA with Cloud Agents at Ship London on 17 June 2026.

cover

May 13Podcast

How AI Is Changing React Native Development and Testing

Zack Ebenfeld joins React Universe On Air to talk about how AI is changing React Native workflows, testing, QA, and day-to-day engineering work.

cover

Apr 14Article

Agent React DevTools: Give AI Agents Access to React Internals

Agent React DevTools gives AI agents access to the React component tree, state, and performance data, enabling deeper debugging and more reliable QA for React and React Native apps.

cover

Mar 19Live Stream

Agent Device, AI workflows, and mobile testing in practice

A discussion on Agent Device, AI-driven workflows, and testing mobile apps using simulators and accessibility data.

cover

Aug 23Podcast

Introducing E2E Tests to React Native Core

This episode of the React Native Show dives into the recent introduction of End-to-End (E2E) testing in RNTester. Join us as we unpack the thought process, technical considerations, and practical implementation of this new approach to testing in React Native.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.