MODULE 2
Modern JavaScript Concepts for React
Deep dive into advanced JavaScript patterns essential for professional React development
Introduction
Module 1 covered the JavaScript basics. Now we'll explore advanced concepts that make your code cleaner, safer, and more predictable—especially important when building React applications.
Immutability
Immutability means not changing data after it's created. This is crucial in React because it helps with performance and predictability.
What is Immutability?
Instead of modifying existing data, create new data:
// ❌ Mutable (bad for React)
const person = { name: "Alice", age: 25 };
person.age = 26; // Modifying original
// ✅ Immutable (good for React)
const person = { name: "Alice", age: 25 };
const updatedPerson = { ...person, age: 26 }; // New objectImmutability with Arrays
const numbers = [1, 2, 3]; // ❌ Mutable numbers.push(4); // Changes original array // ✅ Immutable const newNumbers = [...numbers, 4]; // New array
Reference vs Value
Understanding the difference between reference and value types is fundamental.
Value Types (Primitives)
Stored by value. Copying means creating an independent copy:
let age1 = 25; let age2 = age1; age2 = 26; console.log(age1); // 25 (unchanged) console.log(age2); // 26 (changed)
Reference Types (Objects & Arrays)
Stored by reference. Multiple variables can point to the same object:
const person1 = { name: "Alice" };
const person2 = person1;
person2.name = "Bob";
console.log(person1.name); // "Bob" (changed!)
console.log(person2.name); // "Bob"Why This Matters
const user1 = { name: "Alice" };
const user2 = { name: "Alice" };
console.log(user1 === user2); // false (different objects)
console.log(user1.name === user2.name); // true (same value)Functional Programming Basics
React is built on functional programming principles. Key concepts:
Pure Functions
Same input always produces same output. No side effects:
// ✅ Pure function
const add = (a, b) => a + b;
add(2, 3); // Always returns 5
// ❌ Not pure (changes external state)
let total = 0;
const addToTotal = (num) => {
total += num; // Side effect
return total;
};First-Class Functions
Functions can be treated as values—passed around, returned, etc:
// Pass function as argument
const doMath = (a, b, operation) => {
return operation(a, b);
};
doMath(5, 3, (x, y) => x + y); // 8
doMath(5, 3, (x, y) => x - y); // 2
// Return function
const createGreeter = (greeting) => {
return (name) => `${greeting}, ${name}!`;
};
const sayHi = createGreeter("Hi");
console.log(sayHi("Alice")); // "Hi, Alice!"Higher-Order Functions
A function that takes or returns another function:
withLogger Example
// Higher-order function
const withLogger = (fn) => {
return (...args) => {
console.log("Calling function with:", args);
const result = fn(...args);
console.log("Result:", result);
return result;
};
};
const add = (a, b) => a + b;
const addWithLog = withLogger(add);
addWithLog(5, 3);
// Logs: Calling function with: [5, 3]
// Logs: Result: 8Practical Example: withAuth
const withAuth = (Component) => {
return (props) => {
const user = getCurrentUser();
if (!user) {
return "Please log in";
}
return Component(props);
};
};This pattern is used extensively in React!
Short-Circuiting
Using logical operators to control code execution:
Logical AND (&&)
Execute code only if condition is true:
const user = { name: "Alice", isPremium: true };
// Old way
if (user.isPremium) {
showPremiumFeature();
}
// Modern way
user.isPremium && showPremiumFeature();
// In React (common pattern)
{user.isLoggedIn && }Logical OR (||)
Provide a fallback value:
const greeting = userGreeting || "Hello, Guest!"; const config = userConfig || getDefaultConfig();
Nullish Coalescing (??)
Like ||, but treats 0 and empty string as valid values:
const count = 0; console.log(count || 10); // 10 (0 is falsy) console.log(count ?? 10); // 0 (0 is not nullish)
Optional Chaining (?.)
Safely access nested properties that might not exist:
const user = {
name: "Alice",
address: {
city: "New York"
}
};
// Old way (verbose)
const city = user && user.address && user.address.city;
// Modern way (clean)
const city = user?.address?.city;
// With function calls
const result = user?.getProfile?.();
// With array access
const item = items?.[0];Understanding 'this'
The this keyword is tricky in JavaScript. Understanding it is crucial for React.
In Regular Functions
this depends on how the function is called:
const person = {
name: "Alice",
greet: function() {
console.log("Hello, " + this.name);
}
};
person.greet(); // "Hello, Alice" (this = person)
const greetFunc = person.greet;
greetFunc(); // "Hello, undefined" (this is global/undefined)Arrow Functions (No 'this')
Arrow functions don't have their own this. They use the surrounding context:
const person = {
name: "Alice",
greet: () => {
console.log("Hello, " + this.name); // this is from outer scope
}
};
person.greet(); // "Hello, undefined" (this is global)Why This Matters in React
This is why arrow functions are preferred in React:
// ❌ Problem with regular functions
class Button extends React.Component {
handleClick() {
console.log(this.props); // 'this' is undefined
}
render() {
return ;
}
}
// ✅ Solution with arrow function
class Button extends React.Component {
handleClick = () => {
console.log(this.props); // 'this' works correctly
}
render() {
return ;
}
}this anymore!
Common JavaScript Pitfalls in React
1. Mutating State Directly
// ❌ Wrong - don't do this
const user = { name: "Alice" };
user.name = "Bob"; // React won't detect the change
// ✅ Right - create a new object
const updatedUser = { ...user, name: "Bob" };2. Using Array Index as Key
// ❌ Wrong
{items.map((item, index) => )}
// ✅ Right
{items.map((item) => )}3. Not Handling Asynchronous Operations
// ❌ Wrong
function loadUser() {
const user = fetch("/api/user"); // fetch returns a promise!
console.log(user); // Promise object, not data
}
// ✅ Right
async function loadUser() {
const response = await fetch("/api/user");
const user = await response.json();
console.log(user);
}4. Infinite Loops in useEffect
// ❌ Wrong - infinite loop
useEffect(() => {
setData(something); // Causes effect to run again
});
// ✅ Right - controlled with dependencies
useEffect(() => {
setData(something);
}, [dependency]);5. Sharing Objects Accidentally
// ❌ Wrong
const config = { timeout: 5000 };
function apiCall1() { config.timeout = 10000; }
function apiCall2() { console.log(config.timeout); }
// ✅ Right - use constants or separate instances
const config1 = { timeout: 5000 };
const config2 = { timeout: 5000 };6. Forgetting Default Parameters
// ❌ Wrong
const process = (data) => {
if (data === undefined) data = []; // Verbose
return data.length;
};
// ✅ Right
const process = (data = []) => {
return data.length;
};7. Not Understanding Hoisting
// ❌ Problem with var console.log(x); // undefined (hoisted but not initialized) var x = 5; // ✅ With let/const (avoid this issue) console.log(x); // ReferenceError (better error message) let x = 5;
Best Practices Summary
- Prefer immutability: Create new objects instead of modifying existing ones
- Use pure functions: Avoid side effects where possible
- Understand reference vs value: Objects and arrays are references
- Use arrow functions: They have better
thisbinding - Use optional chaining: Safely access nested properties
- Avoid mutating state directly: Use spread operators to create new objects
- Handle asynchronous code properly: Use async/await or .then()
- Use meaningful variable names: Code readability matters
Practice Exercises
- Create a pure function that takes an object and returns a modified copy
- Write a higher-order function that logs function execution time
- Practice using optional chaining with complex nested objects
- Identify side effects in code samples and explain how to fix them
- Create immutable array operations using spread syntax
Key Takeaways
- Immutability is essential for React—create new data instead of modifying existing data
- Understand reference types (objects, arrays) vs value types (numbers, strings)
- Pure functions are predictable and testable
- Higher-order functions are powerful tools for code reuse
- Arrow functions solve the
thisbinding problem - Optional chaining makes accessing nested data safer
- Short-circuiting can simplify conditional logic
- Be aware of common pitfalls to write better React code