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 object

Immutability with Arrays

const numbers = [1, 2, 3];

// ❌ Mutable
numbers.push(4);  // Changes original array

// ✅ Immutable
const newNumbers = [...numbers, 4];  // New array
💡 Why Immutability Matters in React: React detects changes by comparing old and new data. Immutability makes this comparison fast and reliable.

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)
Reference vs value illustration

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: 8

Practical 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];
💡 Perfect for APIs: Optional chaining is great when working with API responses that might have missing fields.

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 ;
  }
}
💡 Modern React Tip: With functional components and hooks, you rarely need to worry about 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

  1. Prefer immutability: Create new objects instead of modifying existing ones
  2. Use pure functions: Avoid side effects where possible
  3. Understand reference vs value: Objects and arrays are references
  4. Use arrow functions: They have better this binding
  5. Use optional chaining: Safely access nested properties
  6. Avoid mutating state directly: Use spread operators to create new objects
  7. Handle asynchronous code properly: Use async/await or .then()
  8. Use meaningful variable names: Code readability matters

Practice Exercises

  1. Create a pure function that takes an object and returns a modified copy
  2. Write a higher-order function that logs function execution time
  3. Practice using optional chaining with complex nested objects
  4. Identify side effects in code samples and explain how to fix them
  5. Create immutable array operations using spread syntax
Modern JavaScript patterns

Key Takeaways