Brown Bag Session · Bring Your Own Lunch

From seed to canopy: JavaScript, grown into mobile apps.

One language, three habitats. In the next hour we plant JavaScript fundamentals with live, editable code you can run right on this page — then grow that knowledge into React Native, reach the canopy with a Progressive Web App, and look across the valley at Flutter & Dart to see how the other forest grows.

Format · interactive, run-as-we-go Level · beginner-friendly Duration · ~60 min + Q&A

Today's Trail Map

House rule for today: every dark editor box below is live. Change the code, break it, press Run, see what happens. Breaking things on purpose is the fastest way to learn a language.

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.

cell 01 · variables & typeof

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.

cell 02 · functions, three ways

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.

cell 03 · map / filter / reduce

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.

cell 04 · objects & destructuring

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).

cell 05 · template literals · ?. · ??

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.

cell 06 · closures

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.

cell 07 · async / await (watch the timing)

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.

cell 08 · classes
Where JavaScript runs: in the browser (manipulating the page via the DOM), on servers via Node.js, and — as we'll see next — inside native mobile apps via React Native. One language, many habitats. That leverage is the whole reason today's session exists.

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 / HTMLReact NativeNotes
<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 filesStyleSheet.createStyles 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 routingReact Navigation / Expo RouterScreens 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

  • useEffect runs 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".

cell 09 · data → UI, the react way (simulated)
Getting started for real: the community-standard path is Exponpx 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

RungWhat you addWhat you gain
1 · Solid web pageHTML + CSS + your JavaScriptWorks everywhere with a URL.
2 · Responsive + HTTPSViewport meta, flexible layout, TLSPhone-friendly; HTTPS is mandatory for everything below.
3 · Web App ManifestOne JSON file + a link tagName, icons, theme colour — the browser can now offer “Install”.
4 · Service WorkerOne JS file registered from your pageA background proxy that can cache and serve your app offline.
5 · App behavioursCaching 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

PWA or React Native? Same JavaScript, different trade. PWA: one URL, zero install friction, instant updates — but limited deep-device access and a second-class citizen on iOS (installed via Share → Add to Home Screen; some capabilities restricted). React Native: full native APIs, app-store presence and trust — but store review cycles and per-platform builds. Many teams ship both from shared logic.

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

DimensionJavaScript · React NativeDart · Flutter
LanguageDynamic, huge existing talent pool, same language as your web app.Static types catch bugs early; smaller pool, but easy to learn from JS.
RenderingTranslates to real native widgets — apps inherit platform look & feel.Paints its own pixels — identical everywhere, but not "native-feeling" by default.
PerformanceExcellent 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 sharingMassive if you already have React web — components, logic, and developers transfer.iOS + Android + web + desktop from one Dart codebase.
Ecosystemnpm — the largest package registry anywhere; quality varies.pub.dev — smaller, but curated and consistently high quality.
Dev experienceFast 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.
The unsexy truth: both stacks ship world-class apps. The dominant factors are your team's existing skills and your distribution strategy — not benchmark charts. For a JavaScript-fluent team, RN + PWA compounds what you already know; Flutter is a deliberate second forest to settle.

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.