Skip to content
React Course1.0.0 beta
CSEN
React Course1.0.0 beta
CSEN
🏠 Home🧭 Where to start?🔁 Review📄 Cheat sheets📰 What's newℹ️ About the course
0. JavaScript0/22

JavaScript in general

  • 0.1 Variables, types and operators0/2
  • 0.2 Functions and closures0/2
  • 0.3 Objects0/2
  • 0.4 Arrays I: map, filter and friends0/2
  • 0.5 Arrays II: reduce, sorting and grouping0/3
  • 0.6 Conditions and logical operators0/2
  • 0.7 Asynchronous JavaScript0/2
  • 0.8 Modules and built-in tools0/2

JavaScript for React

  • 0.9 References and immutability0/3
  • 0.10 JS patterns you meet everywhere in React0/2

Basics

  • 1. A quick review of the basics0/3
  • 2. State and events (useState)0/3

Hooks in depth

  • 3. Effects (useEffect)0/3
  • 4. Refs (useRef)0/3
  • 5. More complex state (useReducer)0/3
  • 6. Context0/3
  • 7. Custom hooks0/3

Forms

  • 8. Forms and React 19 Actions0/3

Performance

  • 9. Performance and rendering0/3

Patterns

  • 10. Component design patterns0/3

Ecosystem

  • 11. Where the type goes (TypeScript syntax)0/3
  • 12. TypeScript with React0/3
  • 13. Routing (React Router)0/3
  • 14. Data fetching0/3
  • 15. Application state management0/3

Project

  • 16. Final project: Kanban0/2

Summary

  • 17. Summary: the principles of React

Your account

Privacy

Lesson 0.3 · JavaScript in general

Objects

Dot vs. square brackets, destructuring, spread, Object.entries and dynamic keys.

Loading lesson…

💬 Found a mistake, something unclear, or have an idea? Let me know.
← Functions and closuresArrays I: map, filter and friends →

Before we start: an object is an index card

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: value
author: '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.

ℹ️ What's in this lesson
  1. Reading properties: dot vs. square brackets.
  2. Adding, changing and deleting a property.
  3. Shorthand syntax and computed keys.
  4. Destructuring in depth.
  5. Spread: copying and merging objects.
  6. Going through an object: keys, values, entries.
  7. An object as a dictionary.

1. Reading properties

const user = { name: 'Anna', city: 'London', 'favorite color': 'blue' }
user.name // 'Anna' – dot: you know the name when writing the code
user['name'] // 'Anna' – brackets: the same
user['favorite color'] // 'blue' – a key with a space can only be read with brackets
const key = 'city'
user[key] // 'London' – the property name is in a VARIABLE
user.key // undefined – looks for a property literally called "key"
SyntaxWhen
obj.nameYou 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).

Missing and nested properties

const order = {
id: 7,
customer: { name: 'Peter', address: { city: 'Boston' } },
}
order.customer.address.city // 'Boston' – read nested objects dot after dot
order.note // undefined – a property that doesn't exist isn't an error
order.delivery.date // 💥 TypeError: Cannot read properties of undefined
order.delivery?.date // undefined – ?. stops when something is missing (lesson 0.6)

2. Adding, changing, deleting

const user = { name: 'Anna' }
user.age = 31 // adds a new property
user.name = 'Anna S.' // changes an existing one
user['city'] = 'London' // the same with brackets
delete user.age // deletes a property
user // { name: 'Anna S.', city: 'London' }
⚠️ Change it, or copy it?
All of this changes the original object. In plain JS that's fine, but data held by React (state, props) must not be changed like this – instead you make an updated copy (spread below). Lesson 0.9 explains why.

3. Shorthand syntax and computed keys

const name = 'Anna'
const age = 31
const user = { name: name, age: age } // the old way
const user = { name, age } // shorthand: the variable has the same name as the key
// A method – a function stored in a property
const counter = {
count: 0,
increment() { // shorthand for increment: function () { … }
this.count++
},
}

Computed key [ ]

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 form
function 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' }

4. Destructuring

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 once
const { name: userName } = user // renaming: the variable will be called userName
const { role = 'guest' } = user // a default value when the property is missing (or undefined)
const { name: n, role: r = 'guest' } = user // renaming and a default value
const { age, ...rest } = user // rest = { name: 'Anna', city: 'London' }

Nested destructuring

const order = { id: 7, customer: { name: 'Peter', address: { city: 'Boston' } } }
const { customer: { name, address: { city } } } = order
name // 'Peter'
city // 'Boston'
// customer and address weren't created as variables – we just "passed through" them
✨ Don't overdo it
Nested destructuring three levels deep is more of a riddle. const city = order.customer.address.city is often easier to read.

Destructuring in parameters

// The function gets an object and unpacks what it needs right away
function 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 object
users.map(({ name }) => name)

Destructuring arrays

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

5. Spread: copying and merging

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 properties
copy === user // false – they are two different objects
const moved = { ...user, city: 'Boston' } // a copy with an overwritten property
const withAge = { ...user, age: 31 } // a copy with a new property
const defaults = { theme: 'light', lang: 'en', fontSize: 16 }
const saved = { theme: 'dark' }
const settings = { ...defaults, ...saved } // { theme: 'dark', lang: 'en', fontSize: 16 }
⚠️ Order matters – the last one wins
{ ...user, city: 'Boston' } // { name: 'Anna', city: 'Boston' } ✅ overwrites
{ city: 'Boston', ...user } // { city: 'London', name: 'Anna' } ❌ the spread overwrote it back

Conditional properties

const isAdmin = true
const user = {
name: 'Anna',
...(isAdmin ? { role: 'admin' } : {}), // adds the role property only for an admin
}
❓ What does a “shallow” copy mean?

Spread copies only the first level. Nested objects aren't copied – the copy and the original both point to them:

const user = { name: 'Anna', address: { city: 'London' } }
const copy = { ...user }
copy.name = 'Eve' // the original doesn't change ✅
copy.address.city = 'Boston' // 😱 user.address.city changes too – it's THE SAME object
const deep = { ...user, address: { ...user.address, city: 'Boston' } } // ✅ copy the nested one too

Lesson 0.9 on references goes into this in detail.

6. Going through an object

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 properties
Object.keys({}).length === 0 // true – that's how to recognise an empty object
// From pairs back to an object
Object.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
Demo

Object lab

What you see: The user object and buttons with operations. For each one you see the code, an explanation and the result.

Try it:

  1. Compare dot and brackets: brackets can read a property whose name is in a variable.
  2. Click destructuring: city was renamed to town and the missing email got a default value.
  3. Click rest and spread. Rest removes a property, spread overwrites or adds one – both create a new object.
  4. Go through keys, values and entries – three views of the same object.

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…

7. An object as a dictionary

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 conditions
function statusLabel(status) {
if (status === 'new') return 'New order'
if (status === 'paid') return 'Paid'
if (status === 'shipped') return 'On its way'
return 'Unknown status'
}
// ✅ A dictionary
const STATUS_LABELS = {
new: 'New order',
paid: 'Paid',
shipped: 'On its way',
}
const statusLabel = (status) => STATUS_LABELS[status] ?? 'Unknown status'

Counting and looking up by ID

// Counting occurrences
const 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 array
const users = [{ id: 7, name: 'Anna' }, { id: 9, name: 'Peter' }]
const usersById = {}
for (const u of users) {
usersById[u.id] = u
}
usersById[9].name // 'Peter'

Check yourself

Quiz const key = 'city'. How do you read the property whose name is in the variable key?
Quiz What is the result of { ...{ a: 1, b: 2 }, b: 3 }?
Quiz const { name: userName } = user. Which variable is created?
Quiz const copy = { ...user }; copy.address.city = 'Boston'. Does user.address.city change?

Warm-up

Challenge

Business card

Warm-up

Read two properties of an object and make a copy of it with one extra property.

Task (the same text is in the comment at the top of the file)
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…

Challenge

Challenge

Object tools

Medium Bonus challenge

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.

🔒 Bonus challenge

Medium and hard challenges are a bonus for signed-in readers. Signing up is free – just an e-mail, no password.

How we handle your data

Summary

  • A dot when you know the property name, brackets when it's in a variable.
  • A missing property is undefined, but reading from undefined crashes – ?. helps.
  • Shorthand { name } and computed key { [field]: value }.
  • Destructuring can rename (name: n), give a default value (= 'guest') and collect the rest (...rest).
  • Spread makes a shallow copy; properties written later win.
  • Object.keys / values / entries turn an object into an array, fromEntries turns it back.
  • An object as a dictionary replaces a chain of if/else and can count occurrences or look up by id.
🧩 Where do the things not defined here come from (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 = false
tests.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) return
setResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))
})
})
return () => {
ignore = true
}
}, [tests])
const passed = results.filter((r) => r.status === 'ok').length
return (
<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
}