All writing

Engineering04 Nov 20269 min read0 views

Learn JavaScript before React. These are the parts that matter

Most of the pain juniors feel when they move from vanilla JavaScript to React isn't React at all. It's modules, objects and arrays, array methods and loops. Here they are in plain words, with small examples and the resources I'd use to learn them.

Software engineer
A tree of React components resting on a foundation of blocks made of colourful lines of code.

When I moved from plain JavaScript to React, I thought React was the hard part. It wasn't. Every time I got stuck, the problem was a piece of JavaScript I had skipped. An import I didn't understand. A .map() I copied without knowing what it returned. State that "didn't update" because I changed an array in place.

I see the same thing with almost every junior developer I help. So this post is the guide I wish I had back then. It's short on purpose. Read it, try the examples in your browser console, and React will feel a lot friendlier.

React is mostly JavaScript

Look at this small component. It's the kind of thing you'll write in your first week of React.

JSX
import { useState } from "react";
import Card from "./Card.jsx";

export default function TodoList({ items }) {
  const [filter, setFilter] = useState("all");

  const visible = items.filter((item) =>
    filter === "done" ? item.done : true
  );

  return (
    <ul>
      {visible.map((item) => (
        <Card key={item.id} {...item} />
      ))}
    </ul>
  );
}

Only two things here are React: useState and the HTML-looking JSX. Everything else is plain JavaScript: import and export, destructuring ({ items } and [filter, setFilter]), an arrow function, a ternary (? :), .filter(), .map() and the spread ...item.

A small React component with each part highlighted: only useState and the JSX are React; import, export, destructuring, arrow functions, the ternary, filter, map and spread are plain JavaScript.

If any of those look strange, that's what to learn first. Let's go through them one by one.

ES6 and modules: what's the difference?

This confuses a lot of people, so let's clear it up.

ES6 (also called ES2015) is a version of JavaScript. It was a big update in 2015 that added a lot of the things you see in modern code: let and const, arrow functions, template strings, destructuring, spread, classes, promises, and modules.

Modules are one feature that came with ES6. A module is just a file that can share things with other files using export, and use things from other files using import.

So it's not "ES6 or modules". Modules are part of ES6.

Before modules

Before modules, every file shared one big global space. You loaded scripts in the right order and hoped no two files used the same variable name.

HTML
<script src="utils.js"></script>
<script src="app.js"></script>
<!-- app.js can only use utils.js because it was loaded first -->

With modules, each file keeps its variables to itself. It only shares what it exports.

How import and export work

There are two kinds of export: named and default.

JavaScript
// math.js
export const add = (a, b) => a + b;      // named export
export const PI = 3.14;                  // named export
export default function multiply(a, b) { // default export (only one per file)
  return a * b;
}
JavaScript
// app.js
import multiply, { add, PI } from "./math.js";

add(2, 3);       // 5
multiply(2, 3);  // 6
Two files: math.js exports add and PI by name and multiply as the default; app.js imports multiply without braces and add and PI inside braces.

The simple rule:

  • Named exports are imported with curly braces, and the name must match: import { add } from "./math.js".
  • A default export is imported without braces, and you can call it anything: import times from "./math.js" works too.

That's why in React you write import { useState } from "react" (named) but import Card from "./Card.jsx" (default). If you ever see X is not exported or undefined is not a function, check the braces first. It's the most common mistake I see.

What about require() and module.exports?

That's CommonJS, the older module system Node.js used before ES modules existed. You'll still see it in older tutorials and some config files.

JavaScript
// CommonJS (older Node.js)
const express = require("express");
module.exports = { add };

// ES modules (what React projects use)
import express from "express";
export { add };

They do the same job with different words. In a React project (Vite, Next.js), use import and export.

Why does my import work in React but not in a plain HTML file?

In a React project, a tool like Vite or Next.js (called a bundler) reads all your imports and joins your files together for the browser.

In a plain HTML page there's no bundler, so you have to tell the browser the file is a module, and use the full file name:

HTML
<script type="module" src="app.js"></script>
JavaScript
import { add } from "./math.js"; // the ".js" is required here

The ES6 features you'll see on every React page

You don't need all of ES6. These five show up everywhere.

let and const. Use const by default, and let only when you need to reassign. Forget var.

Arrow functions. A shorter way to write a function. When there are no curly braces, the value is returned for you.

JavaScript
const double = (n) => n * 2;          // returns n * 2
const greet = (name) => {
  return `Hi ${name}`;                 // with braces, you write return
};

Template strings. Backticks let you put values inside a string: `Hello ${name}`.

Destructuring. Pull values out of an object or array into variables. React uses this for props and for useState.

JavaScript
const user = { name: "Sara", age: 24 };
const { name, age } = user;           // name = "Sara", age = 24

const [first, second] = ["a", "b"];   // first = "a", second = "b"

Spread (...). Copy the contents of an array or object into a new one.

JavaScript
const a = [1, 2];
const b = [...a, 3];                  // [1, 2, 3]

const user2 = { ...user, age: 25 };   // a copy with a new age
Two newer helpers worth knowing: ?. and ??

These came after ES6, but you'll see them a lot.

JavaScript
user.address?.city   // undefined instead of an error if address is missing
count ?? 0           // use 0 only if count is null or undefined

Data: values, objects and arrays

This is the part that causes most "my React state doesn't update" bugs.

JavaScript has simple values (primitives) like numbers, strings and booleans. When you copy one, you get a real copy.

JavaScript
let a = 5;
let b = a;
b = 10;
console.log(a); // 5, a didn't change

Objects and arrays are different. A variable doesn't hold the array itself. It holds a reference to it, like an address. Copy the variable, and you copy the address, not the array.

JavaScript
const list = [1, 2];
const sameList = list;
sameList.push(3);
console.log(list); // [1, 2, 3], the "original" changed too

Why this matters in React: React decides whether to re-render by checking if you gave it a new array or object. If you change the old one in place, it's still the same address, so React thinks nothing happened.

JavaScript
// Wrong: same array, React may not re-render
todos.push(newTodo);
setTodos(todos);

// Right: a new array with the old items plus the new one
setTodos([...todos, newTodo]);

So, in React, treat arrays and objects in state as read-only. Make a new one instead of changing the old one.

What are Map and Set?

Two more ES6 data structures. You won't need them often in React, but it's good to know they exist.

  • Set is a list with no duplicates: new Set([1, 1, 2]) holds 1 and 2. Handy for removing duplicates: [...new Set(tags)].
  • Map is like an object, but keys can be anything (even objects), and it keeps the order you added things.

For most React state, plain arrays and objects are the better choice.

Array methods: which one do I use?

Array methods are the heart of React code. The trick is to know what each one gives back. Here's the same list with each method:

JavaScript
const products = [
  { name: "Pen", price: 2, inStock: true },
  { name: "Book", price: 12, inStock: false },
  { name: "Bag", price: 30, inStock: true },
];
A products list of Pen ($2), Book ($12, out of stock) and Bag ($30), and what each method gives back: map a new array of names, filter the Pen and Bag, find the Book, some true, reduce 44, forEach undefined.
  • map turns every item into something else, and gives you a new array of the same length.
    products.map((p) => p.name) gives ["Pen", "Book", "Bag"].
  • filter keeps only the items that pass a test, and gives you a new, shorter array.
    products.filter((p) => p.inStock) gives the Pen and the Bag.
  • find gives you the first item that passes, or undefined.
    products.find((p) => p.price > 10) gives the Book.
  • some and every give you true or false. Does any item pass? Do all of them?
    products.some((p) => !p.inStock) gives true.
  • includes checks if a simple value is in the array: ["a", "b"].includes("a") gives true.
  • reduce squeezes the whole array into one value, like a total.
    products.reduce((sum, p) => sum + p.price, 0) gives 44.
  • forEach runs a function for each item and gives back nothing (undefined).
reduce, step by step

reduce scares a lot of people, so here's what happens inside it:

JavaScript
products.reduce((sum, p) => sum + p.price, 0);
// start: sum = 0
// Pen:   sum = 0 + 2   = 2
// Book:  sum = 2 + 12  = 14
// Bag:   sum = 14 + 30 = 44
// result: 44

The 0 at the end is the starting value. Always give one.

Careful: some methods change the original array

These methods change the array you call them on: push, pop, shift, unshift, splice, sort and reverse. In React state, that's the bug we talked about above.

map, filter, slice, concat and spread don't change the original. They give you a new array, which is exactly what React wants.

JavaScript
// sort changes the original. Copy first:
const cheapFirst = [...products].sort((a, b) => a.price - b.price);

// or use the newer non-changing version:
const cheapFirst2 = products.toSorted((a, b) => a.price - b.price);

Loops, and why React code uses map instead of forEach

JavaScript has several ways to loop:

JavaScript
for (let i = 0; i < items.length; i++) { }   // classic for loop
for (const item of items) { }                // for...of: each value of an array
for (const key in user) { }                  // for...in: each key of an object
while (condition) { }                        // while: until the condition is false
items.forEach((item) => { });                // forEach: a method, returns nothing

All of them are fine in plain JavaScript. A simple rule: use for...of for arrays, for...in (or Object.keys) for objects, and a classic for when you need the index or want to break early.

In React, though, you'll almost always see map. Here's why.

Inside JSX's curly braces { }, you can only put an expression, something that gives back a value. React then shows that value on the screen.

  • map gives back a new array of elements. React can show an array.
  • forEach gives back undefined. React shows nothing.
  • A for loop isn't an expression at all, so you can't even put it inside { }.
JSX
// Shows nothing: forEach returns undefined
<ul>{items.forEach((item) => <li>{item}</li>)}</ul>

// Works: map returns an array of <li> elements
<ul>{items.map((item) => <li key={item}>{item}</li>)}</ul>
The same JSX list written with forEach and with map. forEach returns undefined and the screen stays empty; map returns an array of li elements and the list appears.

That's the whole secret. map isn't "better" than forEach. It just gives back a value, and React needs a value. Use forEach when you only want to do something (like log or send each item), and map when you want to turn a list into a new list, like a list of elements.

What is the key prop?

When you render a list, React asks for a key on each item so it can tell the items apart when the list changes. Use something unique and stable, like an id from your data:

JSX
{users.map((user) => <li key={user.id}>{user.name}</li>)}

Avoid using the index (key={i}) when items can be added, removed or reordered.

One more trap: && with numbers

A common way to show something only sometimes is condition && <Element />. It works with true and false, but not with 0:

JSX
{count && <p>{count} new messages</p>}      // shows "0" when count is 0
{count > 0 && <p>{count} new messages</p>}  // shows nothing when count is 0

Make the left side a real true or false, and you're safe.

Resources I recommend

You don't need ten courses. Pick one or two and practice every day.

  • MDN JavaScript Guide: the reference I still use every week. Free, and always up to date. If you only bookmark one page, make it the Array page.
  • javascript.info: The Modern JavaScript Tutorial. Clear chapters with small tasks at the end of each one. Free.
  • Eloquent JavaScript by Marijn Haverbeke: a full book you can read for free online. Great for understanding why things work, not just how.
  • You Don't Know JS Yet by Kyle Simpson: free on GitHub. Read it after the basics, when you want to understand scope, closures and this properly.
  • Meta Front-End Developer Professional Certificate on Coursera: this is the one I'd pick if you want a full path with structure. It's made by Meta (the company behind React), it's for beginners, and it goes in the right order: HTML and CSS, then a whole course on JavaScript, then React Basics and Advanced React, and a final project. You can audit the courses for free, and Coursera offers financial aid if you want the certificate.

And one exercise that teaches more than any course: build a small to-do app in plain JavaScript first. Add, delete, mark as done, filter. Then build the same app again in React. You'll see exactly what React does for you, and you'll never forget it.

Before you start React

If you can say "yes" to these, you're ready:

  • I know the difference between a named and a default import.
  • I can use destructuring and spread without looking them up.
  • I know that arrays and objects are copied by reference.
  • I know what map, filter, find and reduce give back.
  • I know why map works inside JSX and forEach doesn't.

None of this is hard. It just needs a bit of time before React, instead of a lot of confusion during it.

00 comments
Share

Comments

Comments appear after a quick check, to keep spam out. No email needed.

No comments yet. Be the first.

Keep reading