Today's Trail Map
JavaScript, hands-on
Variables, functions, arrays, objects, closures, async — every example is editable and runnable.
II.React Native
Your JS transfers almost wholesale: components, props, state, hooks — and what changes on mobile.
III.Building towards a PWA
The ladder from web page to installable, offline-capable app: manifest, service worker, caching.
IV.Flutter & Dart, compared
The same counter app in both worlds, plus an honest decision table for choosing a stack.
Part I · The Forest Floor
JavaScript, learned by running it
JavaScript is the language of the web — the only language every browser speaks natively. It's dynamically typed (variables can hold anything), single-threaded (one thing at a time, but very good at waiting), and event-driven (code reacts to clicks, timers, and network replies). Master roughly eight ideas and you can read most JavaScript in the wild. Here they are — live.
1 · Variables & types — the soil
Use const by default (the label can't be reassigned), let when a value must change, and forget var exists (it's the legacy way, with confusing scoping). JavaScript's core types: string, number, boolean, undefined, null, object, and array.
2 · Functions — reusable machinery
Functions are values in JavaScript: you can store them in variables, pass them around, and return them from other functions. The modern arrow function (x) => x * 2 is the compact form you'll see everywhere — especially in React.
3 · Arrays & the big three: map, filter, reduce
Arrays are ordered lists. The three methods below are the workhorses of modern JavaScript — and the literal syntax of React lists. map transforms every item, filter keeps some, reduce boils everything down to one value.
4 · Objects, destructuring & spread
Objects group related data under named keys — they're JavaScript's everything-container. Destructuring unpacks them into variables; spread (...) copies and merges them. React props are literally destructured objects, so this syntax pays off double later.
5 · Template literals & modern niceties
Backtick strings interpolate variables with ${…}, and two tiny operators save whole if-statements: optional chaining ?. (reach safely into maybe-missing data) and nullish coalescing ?? (fallback only when a value is null/undefined).
6 · Closures — functions that remember
A closure is a function that keeps access to the variables of the place it was born, even after that place has finished running. It's how JavaScript does private state — and it's the invisible machinery behind React hooks.
7 · Async — promises & await
JavaScript never blocks: slow work (network calls, timers) hands back a Promise — an IOU for a future value. async/await lets you write asynchronous code that reads top-to-bottom like normal code. This is the single most important concept for real-world apps, where nearly everything involves waiting for a server.
8 · Classes — blueprints (you'll mostly read them)
Classes bundle data and behaviour into blueprints for objects. Modern JS (and React) leans functional, so you'll write classes rarely but read them often — and Dart, later today, is built almost entirely from them.
Part II · The Understory
React Native: your JavaScript, on a phone
React Native lets you build genuinely native iOS and Android apps in JavaScript. You describe the UI as components — JavaScript functions that return markup — and React Native renders them as real native widgets, not a webpage in a wrapper. Everything from Part I transfers: arrow functions, destructuring, map, closures, async. What changes is the vocabulary of the UI.
The mental model: UI = f(state)
React's core idea in one line: your UI is a function of your data. You never manually update the screen ("find that label, change its text"). Instead you change the state, and React re-runs your component function and redraws whatever differs. This inversion is 80% of learning React — the rest is vocabulary.
Web words → React Native words
| Web / HTML | React Native | Notes |
|---|---|---|
<div> | <View> | The universal container. Flexbox layout by default. |
<p>, <span> | <Text> | All text must live inside a <Text> — no loose strings. |
<img> | <Image> | Requires explicit width/height. |
<button> | <Pressable> / <Button> | onPress instead of onclick. |
<input> | <TextInput> | onChangeText hands you the string directly. |
| CSS files | StyleSheet.create | Styles are JS objects: camelCase keys, no units (numbers = density-independent pixels). |
| Scrolling page | <ScrollView> / <FlatList> | Nothing scrolls unless you ask. FlatList virtualises long lists. |
| URL routing | React Navigation / Expo Router | Screens on a stack you push and pop, like a deck of cards. |
The same counter, web vs native
Below is one component written twice. Click between the tabs — notice the logic (state, handler, JSX shape) is identical; only the tags and styling dialect change.
import { useState } from "react";
export default function Counter() {
// useState: give React a value to remember + a setter to change it
const [count, setCount] = useState(0);
return (
<div style={{ padding: 24 }}>
<p>You tapped {count} times</p>
<button onClick={() => setCount(count + 1)}>
Tap me
</button>
</div>
);
}import { useState } from "react";
import { View, Text, Pressable, StyleSheet } from "react-native";
export default function Counter() {
const [count, setCount] = useState(0); // identical!
return (
<View style={styles.wrap}>
<Text>You tapped {count} times</Text>
<Pressable onPress={() => setCount(count + 1)}>
<Text style={styles.btn}>Tap me</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
wrap: { padding: 24 }, // numbers, not "24px"
btn: { color: "#D9A441", fontWeight: "600" },
});The four ideas that carry every React Native app
Components
- Plain functions returning JSX (HTML-ish syntax inside JS).
- Compose like Lego:
<ProfileCard>inside<Screen>. - Capitalised names = your components; lowercase = built-ins.
Props
- Inputs passed down from parent to child — read-only.
- Received as one object, destructured:
function Card({ title }). - That's cell 04's destructuring, earning its keep.
State & hooks
useState= memory that survives re-renders.- Change state only via the setter — never mutate directly.
- Immutable updates use spread:
setUser({ ...user, name }).
Effects & data
useEffectruns side-effects (fetching, subscriptions) after render.- Data loading = cell 07's async/await + a loading state flag.
- Pattern:
loading → data | error, render each case.
Rendering a list — Part I pays off
Remember map from cell 03? In React it is literally how lists become UI: map data to components. This runnable cell simulates what React does under the hood — mapping an array of objects into rendered "views".
npx create-expo-app my-app, then scan a QR code with the Expo Go app and your code hot-reloads on your actual phone in seconds. No Xcode or Android Studio needed until you ship.Part III · Reaching the Canopy
Building towards a Progressive Web App
A PWA is a normal website that has climbed high enough to behave like an app: installable to the home screen, launchable full-screen, working offline, optionally sending push notifications. No app store, no review queue, one codebase — your Part I JavaScript, plus two small files. The climb has five rungs:
The ladder
| Rung | What you add | What you gain |
|---|---|---|
| 1 · Solid web page | HTML + CSS + your JavaScript | Works everywhere with a URL. |
| 2 · Responsive + HTTPS | Viewport meta, flexible layout, TLS | Phone-friendly; HTTPS is mandatory for everything below. |
| 3 · Web App Manifest | One JSON file + a link tag | Name, icons, theme colour — the browser can now offer “Install”. |
| 4 · Service Worker | One JS file registered from your page | A background proxy that can cache and serve your app offline. |
| 5 · App behaviours | Caching strategy, offline fallback, (push) | Loads instantly, survives airplane mode, feels native. |
Rung 3 — the manifest
A single JSON file that tells the browser "this site is an app". Link it from your HTML head with <link rel="manifest" href="/manifest.json">.
// manifest.json { "name": "Canopy & Coast Field Guide", "short_name": "Canopy", "start_url": "/", "display": "standalone", // launches without browser chrome "background_color": "#0B1712", "theme_color": "#0B1712", "icons": [ { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" } ] }
Rung 4 — the service worker
A service worker is JavaScript that lives between your app and the network, even when the tab is closed. It intercepts every request your app makes, so it can answer from a cache when the network is gone. It's Part I's async and events, in a new habitat:
// in your page: register the worker (async/await from cell 07!) if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js"); } // sw.js — cache-first strategy const CACHE = "canopy-v1"; const ASSETS = ["/", "/index.html", "/app.js", "/styles.css"]; self.addEventListener("install", e => { // pre-cache the app shell at install time e.waitUntil(caches.open(CACHE).then(c => c.addAll(ASSETS))); }); self.addEventListener("fetch", e => { // cache first, network as fallback → instant loads, offline support e.respondWith( caches.match(e.request).then(hit => hit || fetch(e.request)) ); });
Strategy vocabulary worth knowing: cache-first (speed, for the app shell), network-first (freshness, for API data), stale-while-revalidate (serve cache instantly, refresh it in the background — the crowd favourite).
Your PWA readiness checklist
Tick off what your current project already has — the meter tells you how far up the ladder you are. (Audit real projects with Lighthouse in Chrome DevTools.)
0 / 7 — forest floor
Part IV · The Other Forest
Flutter & Dart: how the neighbouring valley grows
Flutter is Google's cross-platform toolkit; you write it in Dart. The philosophical split from React Native: instead of translating your UI into each platform's native widgets, Flutter paints every pixel itself with its own rendering engine — like a game engine for apps. Result: pixel-identical UI on iOS, Android, web, and desktop, at the cost of leaving the platform's native look behind.
Dart for JavaScript eyes
Dart will look familiar — C-family syntax, async/await, arrow functions — but it's statically typed (the compiler checks types before you run), class-first (cell 08 everywhere), and null-safe (a variable can't be null unless its type says String?).
// dynamic types, runtime surprises possible
const name = "Ted";
let hikes = [2.2, 5.4, 8.7];
const long = hikes.filter(km => km > 3);
const total = long.reduce((s, k) => s + k, 0);
const fetchPark = async () => {
const res = await fetch("/api/park");
return res.json();
};// static types, checked at compile time final String name = "Ted"; final List<double> hikes = [2.2, 5.4, 8.7]; final long = hikes.where((km) => km > 3); // filter → where final total = long.fold(0.0, (s, k) => s + k); // reduce → fold Future<Park> fetchPark() async { // Promise → Future final res = await http.get(Uri.parse("/api/park")); return Park.fromJson(jsonDecode(res.body)); }
The counter, one last time — React Native vs Flutter
Same app, third dialect. Flutter's "everything is a widget" means even padding and centring are classes you nest — verbose, but relentlessly consistent.
function Counter() {
const [count, setCount] = useState(0);
return (
<View style={styles.wrap}>
<Text>You tapped {count} times</Text>
<Pressable onPress={() => setCount(count + 1)}>
<Text>Tap me</Text>
</Pressable>
</View>
);
}class Counter extends StatefulWidget { @override State<Counter> createState() => _CounterState(); } class _CounterState extends State<Counter> { int count = 0; @override Widget build(BuildContext context) { return Column(children: [ Text("You tapped $count times"), ElevatedButton( onPressed: () => setState(() => count++), child: const Text("Tap me"), ), ]); } }
The honest comparison table
| Dimension | JavaScript · React Native | Dart · Flutter |
|---|---|---|
| Language | Dynamic, huge existing talent pool, same language as your web app. | Static types catch bugs early; smaller pool, but easy to learn from JS. |
| Rendering | Translates to real native widgets — apps inherit platform look & feel. | Paints its own pixels — identical everywhere, but not "native-feeling" by default. |
| Performance | Excellent for most apps; JS↔native bridge work continues to shrink overhead. | Compiles ahead-of-time to machine code — very strong, especially for heavy animation. |
| Code sharing | Massive if you already have React web — components, logic, and developers transfer. | iOS + Android + web + desktop from one Dart codebase. |
| Ecosystem | npm — the largest package registry anywhere; quality varies. | pub.dev — smaller, but curated and consistently high quality. |
| Dev experience | Fast Refresh; Expo makes day one delightful. | Hot reload is superb; tooling is famously cohesive. |
| Hiring (esp. fintech/APAC) | Every web dev is 70% of the way there. | Dedicated hires or upskilling time required. |
A decision guide, plainly
Lean JavaScript / RN / PWA when…
- Your team already lives in JavaScript or React.
- You want web + mobile sharing one brain (and much code).
- Distribution friction matters — a PWA needs only a URL.
- Platform-native look and feel is a feature, not a bug.
Lean Flutter / Dart when…
- Pixel-perfect, custom-branded UI must match on every device.
- Animation-heavy or graphically rich interfaces are core.
- You're starting fresh with no web-React investment.
- Compile-time type safety across a large team appeals.
Field Check · Before Q&A
Five quick questions to lock it in
1 · Which array method turns data into UI lists in React?
map transforms each data item into a component — one array in, one array of UI out.
2 · In React Native, all visible text must be wrapped in…
Unlike HTML, loose strings aren't allowed — every character on screen lives inside a <Text> component.
3 · A closure is a function that…
The inner function keeps live access to its birth scope — that's how makeCounter's count stayed private in cell 06.
4 · The two files that upgrade a website into an installable PWA are…
The manifest describes the app (name, icons, display mode); the service worker enables offline caching. Plus HTTPS underneath.
5 · Flutter differs from React Native mainly because it…
Flutter ships its own rendering engine for identical UI everywhere; React Native drives each platform's real native widgets.