JavaScript basics (ES6+)
Before learning React, let's review the important features of modern JavaScript (ES6 and later). You use this knowledge frequently in React development.
Arrow functions
Arrow functions are syntax for writing functions concisely.
Basic syntax
// Traditional function
function greet(name) {
return `Hello, ${name}!`
}
// Arrow function
const greet = (name) => {
return `Hello, ${name}!`
}
// When returning on one line, you can omit the braces and return
const greet = (name) => `Hello, ${name}!`
// When there is one argument, you can also omit the parentheses
const greet = name => `Hello, ${name}!`
Multiple arguments
const add = (a, b) => a + b
const multiply = (a, b, c) => a * b * c
Returning an object
When returning an object literal, you need to wrap it in parentheses.
// NG: the braces are interpreted as a block
const createUser = (name) => { name: name }
// OK: wrap in parentheses
const createUser = (name) => ({ name: name })
// Shorthand for objects
const createUser = (name) => ({ name })
Usage in React
The following is an example of usage in React. The details of components and Props are covered in later chapters; here, it's fine to just get a feel for "so this is how arrow functions are used."
// Component definition (* components are explained in detail in Chapter 5, Props in Chapter 6)
const Button = ({ label }) => <button>{label}</button>
// Event handler (* event handling is explained in detail in Chapter 8)
const handleClick = () => {
console.log('Clicked')
}
// Array operations (* map is explained in detail later in "map, filter, and find on arrays")
const doubled = numbers.map(n => n * 2)
Destructuring
This is syntax for taking values out of an object or array and assigning them to variables.
Object destructuring
const user = {
name: 'Tanaka',
age: 25,
email: 'tanaka@example.com'
}
// Traditional way
const name = user.name
const age = user.age
// Destructuring
const { name, age, email } = user
console.log(name) // 'Tanaka'
console.log(age) // 25
// Giving an alias
const { name: userName, age: userAge } = user
console.log(userName) // 'Tanaka'
// Default value
const { name, role = 'guest' } = user
console.log(role) // 'guest' (because user has no role)
Array destructuring
const colors = ['red', 'green', 'blue']
// Traditional way
const first = colors[0]
const second = colors[1]
// Destructuring
const [first, second, third] = colors
console.log(first) // 'red'
console.log(second) // 'green'
// Take only a specific element
const [, , third] = colors
console.log(third) // 'blue'
// Get the rest of the elements as an array
const [first, ...rest] = colors
console.log(rest) // ['green', 'blue']
Destructuring in function arguments
// Receive an object as an argument
const greet = ({ name, age }) => {
return `Hello, ${name} (age ${age})`
}
greet({ name: 'Tanaka', age: 25 }) // 'Hello, Tanaka (age 25)'
Usage in React
The following is an example of usage in React. The details of each feature are covered in later chapters; here, it's fine to just get a feel for "so this is how destructuring is used."
// Destructuring Props (* Props are explained in detail in Chapter 6)
// Take the values you need out of the argument (props) the component receives
const UserCard = ({ name, email, role = 'user' }) => (
<div>
<h2>{name}</h2>
<p>{email}</p>
<span>{role}</span>
</div>
)
// Destructuring useState (* useState is explained in detail in Chapter 7)
// useState(0) returns the array [current value, function to update the value]
// Here we receive count = 0 (the initial value) and setCount = the update function
const [count, setCount] = useState(0)
// Destructuring useReducer (* useReducer is explained in detail in Chapter 14)
const [state, dispatch] = useReducer(reducer, initialState)
Spread syntax
This is syntax for expanding an array or object.
Spreading an array
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
// Combining arrays
const combined = [...arr1, ...arr2]
console.log(combined) // [1, 2, 3, 4, 5, 6]
// Copying an array
const copy = [...arr1]
// Adding an element
const withNewItem = [...arr1, 4] // [1, 2, 3, 4]
const withFirst = [0, ...arr1] // [0, 1, 2, 3]
Spreading an object
const user = { name: 'Tanaka', age: 25 }
// Copying an object
const copy = { ...user }
// Adding or overwriting a property
const updated = { ...user, email: 'tanaka@example.com' }
// { name: 'Tanaka', age: 25, email: 'tanaka@example.com' }
const older = { ...user, age: 26 }
// { name: 'Tanaka', age: 26 }
// Merging objects
const defaults = { theme: 'light', language: 'ja' }
const settings = { theme: 'dark' }
const merged = { ...defaults, ...settings }
// { theme: 'dark', language: 'ja' }
Usage in React
In React, you must not modify state directly. Always create a new object or array using spread syntax or similar. This is called an "immutable update."
// ❌ NG: direct mutation (React cannot detect the change)
user.name = 'Suzuki'
setUser(user)
// ✅ OK: create a new object (the change can be detected)
setUser({ ...user, name: 'Suzuki' })
Why is direct mutation bad?
React decides whether to re-render based on "whether the object reference changed." If you mutate directly, the reference stays the same, so the screen does not update. This is covered in detail in Chapter 7.
// Forwarding Props
const Button = (props) => <button {...props} />
// Updating object state (* useState is explained in detail in Chapter 7)
const [user, setUser] = useState({ name: '', age: 0 })
setUser({ ...user, name: 'Tanaka' })
// Updating array state
const [items, setItems] = useState([])
setItems([...items, newItem]) // Add to the end
setItems([newItem, ...items]) // Add to the beginning
Rest parameters
This is syntax for receiving the remaining arguments of a function as an array.
// Separate the first argument from the rest
const log = (first, ...rest) => {
console.log('First:', first)
console.log('Rest:', rest)
}
log('a', 'b', 'c', 'd')
// First: a
// Rest: ['b', 'c', 'd']
// Used in array destructuring
const [head, ...tail] = [1, 2, 3, 4, 5]
console.log(head) // 1
console.log(tail) // [2, 3, 4, 5]
// Used in object destructuring
const { name, ...otherProps } = { name: 'Tanaka', age: 25, email: 'tanaka@example.com' }
console.log(name) // 'Tanaka'
console.log(otherProps) // { age: 25, email: 'tanaka@example.com' }
Usage in React
Rest parameters are often used in the pattern of taking out only specific props in a component and forwarding the rest as-is.
// Take out only className and forward the remaining props to button as-is
const Button = ({ className, ...rest }) => (
<button className={`btn ${className}`} {...rest} />
)
// Usage
<Button className="primary" onClick={handleClick} disabled>
Submit
</Button>
// → className is processed and applied
// → onClick, disabled, and children are forwarded to <button> as-is
This pattern is the basic technique for wrapping and extending an existing HTML element or component. It is used frequently in UI component libraries.
Note that onClick (event handling) is covered in Chapter 8, and children (child elements) in Chapter 6. Here, it's enough to grasp just "you can forward the remaining props together."
Template literals
This is a way of writing strings using backticks.
const name = 'Tanaka'
const age = 25
// Traditional way
const message = name + ' is ' + age + ' years old'
// Template literal
const message = `${name} is ${age} years old`
// Multi-line string
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`
// Embedding an expression
const result = `Total: ${1 + 2 + 3}` // 'Total: 6'
The ternary operator
This is an operator that returns a value based on a condition.
const age = 20
// if-else statement
let message
if (age >= 18) {
message = 'Adult'
} else {
message = 'Minor'
}
// Ternary operator
const message = age >= 18 ? 'Adult' : 'Minor'
// Nesting (be careful, it's hard to read)
const category = age < 13 ? 'Child' : age < 20 ? 'Teen' : 'Adult'
Usage in React
// Conditional rendering
const Message = ({ isLoggedIn }) => (
<p>{isLoggedIn ? 'Welcome!' : 'Please log in'}</p>
)
// Usage
<Message isLoggedIn /> // → 'Welcome!'
<Message isLoggedIn={false} /> // → 'Please log in'
// Conditional class name
const Button = ({ isPrimary }) => (
<button className={isPrimary ? 'btn-primary' : 'btn-secondary'}>
Button
</button>
)
// Usage
<Button isPrimary>Submit</Button> // isPrimary = true → 'btn-primary'
<Button>Cancel</Button> // isPrimary = undefined → 'btn-secondary'
// Conditional attribute
const Input = ({ isDisabled }) => (
<input disabled={isDisabled ? true : undefined} />
)
// Usage
<Input isDisabled /> // → <input disabled />
<Input /> // → <input /> (no disabled attribute)
The correct way to pass boolean props
// ✅ Correct ways to write it
<Button isPrimary={true} /> // explicitly true
<Button isPrimary /> // shorthand (this is also true)
<Button isPrimary={false} /> // explicitly false
<Button /> // omitted (undefined, so treated as falsy)
// ❌ Ways to avoid
<Button isPrimary="true" /> // the string "true" is passed (a source of bugs)
If you write isPrimary="true", the string "true" is passed. Strings are truthy, so it may appear to work, but it becomes a source of unintended bugs, such as the comparison isPrimary === true evaluating to false.
Logical operators (&&, ||, ??)
The AND operator (&&)
If the left side is truthy, it returns the right side; if falsy, it returns the left side.
const a = true && 'Hello' // 'Hello'
const b = false && 'Hello' // false
const c = '' && 'Hello' // ''
const d = 0 && 'Hello' // 0
The OR operator (||)
If the left side is truthy, it returns the left side; if falsy, it returns the right side.
const a = true || 'Default' // true
const b = false || 'Default' // 'Default'
const c = '' || 'Default' // 'Default'
const d = 0 || 'Default' // 'Default'
// Setting a default value
const name = inputName || 'No Name'
Why does it behave this way? (short-circuit evaluation)
&& and || work via a mechanism called "short-circuit evaluation."
For && (AND):
- If the left side is falsy, there's no need to evaluate the right side (it's falsy either way)
- → return the left side as-is and stop
- If the left side is truthy, the result depends on the right side
- → evaluate the right side and return it
For || (OR):
- If the left side is truthy, there's no need to evaluate the right side (it's truthy either way)
- → return the left side as-is and stop
- If the left side is falsy, the result depends on the right side
- → evaluate the right side and return it
React leverages this mechanism for conditional rendering and setting default values.
The nullish coalescing operator (??)
It returns the right side only when the left side is null or undefined.
const a = null ?? 'Default' // 'Default'
const b = undefined ?? 'Default' // 'Default'
const c = '' ?? 'Default' // '' (an empty string is not nullish)
const d = 0 ?? 'Default' // 0 (0 is not nullish)
Usage in React
// Conditional rendering with &&
const Notification = ({ count }) => (
<div>
{count > 0 && <span className="badge">{count}</span>}
</div>
)
// Fallback with ||
// ⚠️ Note that even when name is an empty string '', it becomes 'No Name'
const UserName = ({ name }) => (
<p>{name || 'No Name'}</p>
)
// Handling nullish with ??
const Counter = ({ initialCount }) => {
// When you want to treat 0 as a valid value too
const count = initialCount ?? 10
return <p>Count: {count}</p>
}
truthy and falsy
In JavaScript, you can also evaluate non-boolean values in a condition.
Falsy values
All of the following values are evaluated as false.
false
0
-0
0n // BigInt 0
'' // empty string
null
undefined
NaN
Truthy values
All values other than falsy ones are evaluated as true.
true
1
'hello' // a non-empty string
[] // an empty array (careful!)
{} // an empty object (careful!)
() => {} // a function
An empty array [] and an empty object {} are truthy! This is a common pitfall for React beginners. You might think "it's empty, so it's falsy," but in JavaScript an array and an object are truthy even when empty, because they "exist."
A common mistake
// Note that an empty array is truthy
const items = []
if (items) {
console.log('The array exists') // this runs
}
// Check whether the array is empty with .length
if (items.length > 0) {
console.log('Has elements')
}
// Or
if (items.length) {
console.log('Has elements')
}
Usage in React
// ❌ A common mistake: it's displayed even for an empty array
const TodoList = ({ todos }) => (
<div>
{todos && <p>{todos.length} Todos</p>} {/* displayed even if todos is [] */}
</div>
)
// ✅ The correct way: check with length
const TodoList = ({ todos }) => (
<div>
{todos.length > 0 && <p>{todos.length} Todos</p>}
</div>
)
// ✅ When you also want to show a message for the empty case
const TodoList = ({ todos }) => (
<div>
{todos.length > 0 ? (
<ul>
{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
</ul>
) : (
<p>There are no Todos</p>
)}
</div>
)
map, filter, and find on arrays
map: transform each element
const numbers = [1, 2, 3, 4, 5]
// Double each element
const doubled = numbers.map(n => n * 2)
// [2, 4, 6, 8, 10]
// Extract a specific property from objects
const users = [
{ id: 1, name: 'Tanaka' },
{ id: 2, name: 'Suzuki' }
]
const names = users.map(user => user.name)
// ['Tanaka', 'Suzuki']
filter: extract elements that match a condition
const numbers = [1, 2, 3, 4, 5]
// Extract only even numbers
const evens = numbers.filter(n => n % 2 === 0)
// [2, 4]
// Filtering objects
const users = [
{ name: 'Tanaka', active: true },
{ name: 'Suzuki', active: false },
{ name: 'Sato', active: true }
]
const activeUsers = users.filter(user => user.active)
// [{ name: 'Tanaka', active: true }, { name: 'Sato', active: true }]
find: get the first element that matches a condition
const users = [
{ id: 1, name: 'Tanaka' },
{ id: 2, name: 'Suzuki' },
{ id: 3, name: 'Sato' }
]
const user = users.find(u => u.id === 2)
// { id: 2, name: 'Suzuki' }
// Returns undefined if not found
const notFound = users.find(u => u.id === 99)
// undefined
Usage in React
The key prop is required! React uses it to identify each element in a list. Without a key, a warning appears, and it becomes a source of performance issues and bugs. Normally you use the data's id. The array index (index) is not recommended, because it causes problems when the order of elements changes.
// Rendering a list
const UserList = ({ users }) => (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li> {/* key={user.id} is important */}
))}
</ul>
)
// Filtering + rendering
const ActiveUserList = ({ users }) => (
<ul>
{users
.filter(user => user.active)
.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
Promise and async/await
This is the mechanism for handling asynchronous processing.
The basics of Promise
// A function that returns a Promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true
if (success) {
resolve({ data: 'data' })
} else {
reject(new Error('An error occurred'))
}
}, 1000)
})
}
// Receive the result with then
fetchData()
.then(result => {
console.log(result.data)
})
.catch(error => {
console.error(error.message)
})
async/await
This is syntax for handling Promises more intuitively.
// Defining an async function
const getData = async () => {
try {
const response = await fetch('https://api.example.com/data')
const data = await response.json()
return data
} catch (error) {
console.error('Error:', error)
throw error
}
}
// Usage
const main = async () => {
const data = await getData()
console.log(data)
}
Multiple asynchronous operations
// Sequential execution: when the second operation depends on the result of the first
const sequential = async () => {
const user = await fetchUser() // 1. First get the user
const posts = await fetchPosts(user.id) // 2. Get the posts with that user's ID
return { user, posts }
}
// Parallel execution: run independent operations at the same time
const parallel = async () => {
const [users, products] = await Promise.all([
fetchUsers(), // run at the same time
fetchProducts() // run at the same time
])
return { users, products }
}
Key points for choosing between them
- Sequential execution: when a later operation needs the result of an earlier one (e.g., user ID → that user's posts)
- Parallel execution: when the operations are independent of each other (e.g., fetching a user list and a product list at the same time)
Parallel execution can greatly reduce the processing time. For example, if you run operations that each take 1 second sequentially, it takes 2 seconds; in parallel, it completes in about 1 second.
Usage in React
The following is an example of using async/await inside a React component. It includes concepts you haven't learned yet, such as useState (Chapter 7) and useEffect (Chapter 11), but here it's fine to just get a feel for "so this is how async/await is used."
const UserProfile = ({ userId }) => {
// useState (* learned in Chapter 7)
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
// Fetch data inside useEffect (* learned in Chapter 11)
useEffect(() => {
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`)
if (!response.ok) throw new Error('Failed to fetch')
const data = await response.json()
setUser(data)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
fetchUser()
}, [userId])
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error}</p>
return <div>{user.name}</div>
}
ES Modules (import/export)
This is the mechanism for splitting and managing modules. In React, you manage every component and utility as a module, so this syntax is essential knowledge.
Naming conventions in React projects
- Component file names are PascalCase (e.g.,
Button.tsx,UserProfile.tsx) - Match the file name to the component name
- Utility functions are camelCase (e.g.,
utils.ts,formatDate.ts)
Named exports
// utils.js
export const add = (a, b) => a + b
export const subtract = (a, b) => a - b
export const PI = 3.14159
// An alternative way
const multiply = (a, b) => a * b
const divide = (a, b) => a / b
export { multiply, divide }
Named imports
// Import only specific functions
import { add, subtract } from './utils'
// Give an alias
import { add as sum } from './utils'
// Import everything together
import * as utils from './utils'
utils.add(1, 2)
Default exports
// Button.tsx
// Component definition (* explained in detail in Chapter 5)
const Button = ({ label }) => <button>{label}</button>
export default Button
// An alternative way
export default function Button({ label }) {
return <button>{label}</button>
}
Default imports
// You can import it under any name
import Button from './Button'
import MyButton from './Button' // the same thing under a different name
Mixed
// utils.js
export const helper = () => {}
export default function main() {}
// Import
import main, { helper } from './utils'
Choosing between named and default
| Feature | Named export | Default export |
|---|---|---|
| Number per file | Multiple are OK | Only one |
| Name when importing | Fixed (use as to change) | You can decide freely |
| Import syntax | { } is required | { } is not needed |
// Named: you need to match the name
import { add, subtract } from './utils'
import { add as sum } from './utils' // use as when you want an alias
// Default: you can import under any name you like
import Button from './Button'
import MyButton from './Button' // this also refers to the same thing
Why are there two kinds?
A default export = the declaration "this is the main thing in this file!"
- Suited to React, where it's one component per file
- You can tell what it is from the file name, so the import name can be free
A named export = the declaration "this file has multiple things."
- Suited to collections of utility functions, type definitions, constants, and so on
- The name is fixed, so consistency within the team is maintained
A common pattern in React
// Exporting a component
// components/Button.tsx
export const Button = ({ label }) => <button>{label}</button>
// Or
const Button = ({ label }) => <button>{label}</button>
export default Button
// Re-exporting in an index file
// components/index.ts
export { Button } from './Button'
export { Input } from './Input'
export { Card } from './Card'
// The consuming side
import { Button, Input, Card } from './components'
Named export or default export — which should you use?
The React community has both schools of thought:
- Pro named export: the name is fixed when importing, so it's more searchable
- Pro default export: it makes "one component per file" clear
This book introduces both, but it's best to follow your team's conventions.
Optional chaining (?.)
This is syntax for safely accessing nested properties. When handling data fetched from an API or optional Props, it prevents errors from undefined or null.
Common situations
- Data fetched from an API (when the shape of the response is uncertain)
- Optional Props (values that might not be passed)
- Nested object structures (like
user.profile.settings.theme)
const user = {
name: 'Tanaka',
address: {
city: 'Tokyo'
}
}
// Traditional way
const city = user && user.address && user.address.city
// Optional chaining
const city = user?.address?.city // 'Tokyo'
// When a property does not exist
const user2 = { name: 'Suzuki' }
const city2 = user2?.address?.city // undefined (no error)
// Accessing array elements
const arr = [1, 2, 3]
const first = arr?.[0] // 1
const tenth = arr?.[9] // undefined
// Calling a method
const result = obj?.method?.()
The difference from the traditional &&
The approach using && leverages the property that if the left side is falsy, the right side is not evaluated. Optional chaining lets you write the same thing more concisely.
// Traditional: long and hard to read
const city = user && user.address && user.address.city
// Optional chaining: concise
const city = user?.address?.city
However, be careful when you want to treat 0 or '' (an empty string) as a valid value. && treats these as falsy, but ?. skips only null and undefined.
Usage in React
const UserProfile = ({ user }) => (
<div>
<p>Name: {user?.name ?? 'Unknown'}</p>
<p>Address: {user?.address?.city ?? 'Not set'}</p>
<p>Friend: {user?.friends?.[0]?.name ?? 'None'}</p>
</div>
)
Summary
The ES6+ features you learned in this chapter are used frequently in React development.
A study tip
You don't need to memorize everything. While actually writing React, when you think "wait, how do I write this again?", come back to this chapter. As you use them, they'll come naturally.
| Feature | Main uses in React |
|---|---|
| Arrow functions | Component definitions, event handlers |
| Destructuring | Props, useState, useReducer |
| Spread syntax | Forwarding Props, state updates (immutable) |
| Rest parameters | The Props forwarding pattern |
| Template literals | Generating dynamic strings |
| The ternary operator | Conditional rendering |
| &&, ||, ?? | Conditional rendering, default values |
| truthy and falsy | Understanding conditionals |
| map, filter | Rendering lists |
| async/await | Data fetching |
| import/export | Module management |
| ?. (optional chaining) | Safe property access |
Once you understand these basics, in the next chapter let's actually set up the React development environment.
The especially important points in this chapter
- Immutable updates with spread syntax — essential for React's state management
- Conditional rendering with
&&— used frequently in JSX - Combining
?.and??— the standard pattern for safe data access