Lesson 0.3 · JavaScript in general
Objects
Dot vs. square brackets, destructuring, spread, Object.entries and dynamic keys.
Loading lesson…
Lesson 0.3 · JavaScript in general
Dot vs. square brackets, destructuring, spread, Object.entries and dynamic keys.
Loading lesson…
Picture the card catalogue in a library. Every card describes one book and has labelled fields: title, author, year. An object in JavaScript is exactly such a card – a set of named fields (properties), each with a value.
const book = {title: 'The Little Prince', // key: valueauthor: 'Saint-Exupéry',year: 1943,available: true,}
Objects are everywhere: a user, a product in the cart, the app's settings, a response from the server. In React, the props a component receives are an object too. If you can handle objects well, you write shorter and more readable code.
const user = { name: 'Anna', city: 'London', 'favorite color': 'blue' }user.name // 'Anna' – dot: you know the name when writing the codeuser['name'] // 'Anna' – brackets: the sameuser['favorite color'] // 'blue' – a key with a space can only be read with bracketsconst key = 'city'user[key] // 'London' – the property name is in a VARIABLEuser.key // undefined – looks for a property literally called "key"
| Syntax | When |
|---|---|
obj.name | You know the property name when writing the code – most cases. |
obj[key] | The name is in a variable or computed at run time (a form with many fields, a dictionary, a loop). |
const order = {id: 7,customer: { name: 'Peter', address: { city: 'Boston' } },}order.customer.address.city // 'Boston' – read nested objects dot after dotorder.note // undefined – a property that doesn't exist isn't an errororder.delivery.date // 💥 TypeError: Cannot read properties of undefinedorder.delivery?.date // undefined – ?. stops when something is missing (lesson 0.6)
const user = { name: 'Anna' }user.age = 31 // adds a new propertyuser.name = 'Anna S.' // changes an existing oneuser['city'] = 'London' // the same with bracketsdelete user.age // deletes a propertyuser // { name: 'Anna S.', city: 'London' }
const name = 'Anna'const age = 31const user = { name: name, age: age } // the old wayconst user = { name, age } // shorthand: the variable has the same name as the key// A method – a function stored in a propertyconst counter = {count: 0,increment() { // shorthand for increment: function () { … }this.count++},}
When you only know the property name at run time (it's in a variable), put it in square brackets when creating the object:
const field = 'email'const value = 'anna@example.com'const update = { [field]: value } // { email: 'anna@example.com' }const wrong = { field: value } // { field: 'anna@example.com' } – literally "field"// Typical use: one function for all the fields of a formfunction updateForm(form, field, value) {return { ...form, [field]: value }}updateForm({ name: '', email: '' }, 'name', 'Anna') // { name: 'Anna', email: '' }const prefix = 'color'const theme = { [`${prefix}Primary`]: 'blue' } // { colorPrimary: 'blue' }
Destructuring is unpacking a box: instead of "take the box and from it the thing labelled name" you say straight away "I want the things name and city".
const user = { name: 'Anna', city: 'London', age: 31 }const { name, city } = user // two variables at onceconst { name: userName } = user // renaming: the variable will be called userNameconst { role = 'guest' } = user // a default value when the property is missing (or undefined)const { name: n, role: r = 'guest' } = user // renaming and a default valueconst { age, ...rest } = user // rest = { name: 'Anna', city: 'London' }
const order = { id: 7, customer: { name: 'Peter', address: { city: 'Boston' } } }const { customer: { name, address: { city } } } = ordername // 'Peter'city // 'Boston'// customer and address weren't created as variables – we just "passed through" them
// The function gets an object and unpacks what it needs right awayfunction formatUser({ name, city = 'unknown' }) {return `${name} (${city})`}formatUser({ name: 'Anna', city: 'London', age: 31 }) // 'Anna (London)'// This is exactly what a React component looks like:function UserCard({ name, city }) { … }// And in a callback – every item of the array is an objectusers.map(({ name }) => name)
An array isn't unpacked by name, but by position:
const [first, second] = ['apple', 'pear', 'plum'] // 'apple', 'pear'const [, , third] = ['apple', 'pear', 'plum'] // skipping: 'plum'const [head, ...tail] = [1, 2, 3, 4] // 1 and [2, 3, 4]let a = 1, b = 2;[a, b] = [b, a] // swapping: a = 2, b = 1 (the leading semicolon is needed here)const [count, setCount] = useState(0) // ← you'll see this all the time in React
Three dots before an object mean "pour all its properties in here".
const user = { name: 'Anna', city: 'London' }const copy = { ...user } // a shallow copy: a new object, the same propertiescopy === user // false – they are two different objectsconst moved = { ...user, city: 'Boston' } // a copy with an overwritten propertyconst withAge = { ...user, age: 31 } // a copy with a new propertyconst defaults = { theme: 'light', lang: 'en', fontSize: 16 }const saved = { theme: 'dark' }const settings = { ...defaults, ...saved } // { theme: 'dark', lang: 'en', fontSize: 16 }
const isAdmin = trueconst user = {name: 'Anna',...(isAdmin ? { role: 'admin' } : {}), // adds the role property only for an admin}
const prices = { apple: 12, pear: 15, plum: 8 }Object.keys(prices) // ['apple', 'pear', 'plum']Object.values(prices) // [12, 15, 8]Object.entries(prices) // [['apple', 12], ['pear', 15], ['plum', 8]]for (const [fruit, price] of Object.entries(prices)) {console.log(`${fruit} costs ${price} cents`)}Object.keys(prices).length // 3 – the number of propertiesObject.keys({}).length === 0 // true – that's how to recognise an empty object// From pairs back to an objectObject.fromEntries([['a', 1], ['b', 2]]) // { a: 1, b: 2 }
The combination entries → change → fromEntries is a common way to "recalculate" all the values of an object. Array methods like map and filter come in the next lesson; then you'll be able to write it on one line.
// Raise every price by 10 %const result = {}for (const [fruit, price] of Object.entries(prices)) {result[fruit] = Math.round(price * 1.1)}// result = { apple: 13, pear: 17, plum: 9 }'apple' in prices // true – does the object have this property?Object.hasOwn(prices, 'banana') // false
What you see: The user object and buttons with operations. For each one you see the code, an explanation and the result.
Try it:
city was renamed to town and the missing email got a default value.The takeaway: An object can be read with a dot or with brackets, unpacked with destructuring, copied with spread and turned into an array with Object.entries.
Loading the interactive part…
An object doesn't have to describe one thing. It often serves as a dictionary (a lookup table): key → value. That replaces long chains of if/else:
// ❌ A chain of conditionsfunction statusLabel(status) {if (status === 'new') return 'New order'if (status === 'paid') return 'Paid'if (status === 'shipped') return 'On its way'return 'Unknown status'}// ✅ A dictionaryconst STATUS_LABELS = {new: 'New order',paid: 'Paid',shipped: 'On its way',}const statusLabel = (status) => STATUS_LABELS[status] ?? 'Unknown status'
// Counting occurrencesconst votes = ['yes', 'no', 'yes', 'yes']const counts = {}for (const v of votes) {counts[v] = (counts[v] ?? 0) + 1}counts // { yes: 3, no: 1 }// Fast lookup by id instead of going through the whole arrayconst users = [{ id: 7, name: 'Anna' }, { id: 9, name: 'Peter' }]const usersById = {}for (const u of users) {usersById[u.id] = u}usersById[9].name // 'Peter'
Read two properties of an object and make a copy of it with one extra property.
CHALLENGE: Business card
1. fullName(person) should return the first and last name with a space: 'Anna Smith'.
Read the properties with a dot: person.first, person.last
2. withCity(person, 'Boston') should return a NEW object with the city added: { ...person, city }Loading the interactive part…
Six small functions for working with objects: dynamic keys, removing a property, picking properties, counting and recalculating values. The inputs are frozen, so you can't change them by accident.
Medium and hard challenges are a bonus for signed-in readers. Signing up is free – just an e-mail, no password.
undefined, but reading from undefined crashes – ?. helps.{ name } and computed key { [field]: value }.name: n), give a default value (= 'guest') and collect the rest (...rest).Object.keys / values / entries turn an object into an array, fromEntries turns it back.Checks, deepFreeze, Check)Checks, deepFreeze, Check – from the file src/course/checks.tsx. The course helper for function-style challenges: runs the tests and shows ✅/❌. You don't need to know how it works inside – it just tells you which steps are done./** One test: a description and a function returning true (passed) or false. It may be async. */export type Check = [name: string, test: () => boolean | Promise<boolean>]/*** Runs the tests of a challenge and shows ✅ or ❌ for each.* An error thrown in a test (e.g. when trying to change a frozen object) is shown under the test.*/export function Checks({ tests }: { tests: Check[] }) {const t = useT()const [results, setResults] = useState<Result[]>(() => tests.map(() => ({ status: 'pending' })))useEffect(() => {let ignore = falsetests.forEach(([, test], i) => {Promise.resolve().then(test).then((ok): Result => (ok ? { status: 'ok' } : { status: 'fail' }),(e): Result => ({ status: 'fail', error: e instanceof Error ? e.message : String(e) }),).then((result) => {if (ignore) returnsetResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))})})return () => {ignore = true}}, [tests])const passed = results.filter((r) => r.status === 'ok').lengthreturn (<div className="stack"><strong className={passed === tests.length ? 'ok' : undefined}>{t.checksPassed(passed, tests.length)}</strong><ul className="list">{tests.map(([name], i) => {const r = results[i] ?? { status: 'pending' }return (<li key={name}>{r.status === 'pending' ? '⏳' : r.status === 'ok' ? '✅' : '❌'} {name}{r.status === 'fail' && r.error && (<div className="bad muted">{t.checksError} {r.error}</div>)}</li>)})}</ul></div>)}/** Freezes an object and all nested objects and arrays – any attempt to change them throws. */export function deepFreeze<T>(value: T): T {if (value && typeof value === 'object') {Object.values(value).forEach(deepFreeze)Object.freeze(value)}return value}