Functions and Scope - React Prep Review
Introduction
Why This Review Matters
After months writing JavaScript, you remember function doSomething() {}. But do you remember why arrow functions are cleaner for event handlers? When closures capture variables and when they don't? How scope rules affect the component patterns you'll write?
Before diving into React's function components and hooks, let's refresh the function concepts that frameworks assume you know cold. We'll use console experiments to rediscover JavaScript's function behavior - the stuff that trips up developers during technical interviews and debugging sessions.
What We'll Cover
- Function declarations vs expressions - and why the difference matters for hoisting
- Arrow functions - why they're React's preferred syntax and how
thisbehaves - Scope rules - lexical scope, closures, and how they power modern JavaScript patterns
- First-class functions - functions as values, callbacks, and higher-order patterns
- Modern parameter features - destructuring, defaults, rest/spread
Open your browser's Developer Console (F12 → Console tab). Every concept gets hands-on practice there.
Core Concept Overview
Function Syntax Review: Traditional vs Arrow
Let's review both function syntaxes you'll see in React:
/**
* Function Declaration - traditional style.
*
* Use a 'verb' phrase to name it.
*/
function greetUser(name) {
return `Hello, ${name}!`;
}
// Arrow Function - modern React style
const greetUser = (name) => {
return `Hello, ${name}!`;
};
// Concise arrow function (implicit return)
const greetUser = (name) => `Hello, ${name}!`;
Why React developers prefer arrow functions:
- Cleaner, more concise syntax
- Implicit returns for simple transformations
- Consistent modern JavaScript style
- Perfect for event handlers and callbacks
/**
* Function component - returns HTML string
*
* Use a capitalized 'noun' phrase to name it.
*/
const Welcome = (props) => {
return `<h1>Hello, ${props.name}!</h1>`;
};
Arrow Function Rules to Remember
- Single parameter:
user => user.name(parentheses optional) - Multiple parameters:
(a, b) => a + b(parentheses required) - No parameters:
() => console.log('hello')(parentheses required) - Single expression: implicit return (no
returnkeyword needed) - Multiple statements: explicit return with curly braces required
Closures: The Foundation of React Hooks
Closures are everywhere in React - they're what makes hooks work! Let's understand them with a realistic shopping cart example:
// A closure is when an inner function "remembers" variables from its outer scope
function createShoppingCart() {
let items = []; // This stays "private" - like React state!
return {
// These functions "close over" the items variable
addItem: (product, price) => {
items.push({ product, price });
console.log(`Added ${product} for $${price}`);
},
removeItem: (product) => {
items = items.filter((item) => item.product !== product);
console.log(`Removed ${product}`);
},
getTotal: () => {
return items.reduce((total, item) => total + item.price, 0);
},
getItems: () => {
return [...items]; // Return a copy (don't expose the original)
},
};
}
// Each cart gets its own private "items" array
const userCart = createShoppingCart();
const adminCart = createShoppingCart();
// Test the closures in action
userCart.addItem("Coffee", 4.5);
userCart.addItem("Muffin", 2.25);
console.log("User total:", userCart.getTotal()); // $6.75
adminCart.addItem("Laptop", 999.99);
console.log("Admin total:", adminCart.getTotal()); // $999.99
// The carts are completely separate!
console.log("User items:", userCart.getItems().length); // 2
console.log("Admin items:", adminCart.getItems().length); // 1
Why this matters for React:
- Each component instance gets its own "private" state (like our items array)
- Functions can access and modify that state (like our cart methods)
- The state stays private - other components can't accidentally break it
- This is exactly how
useStateworks under the hood!
Hands-On Application
Exercise 1: Build a Counter with Closures
Let's create a counter that works like React's useState. Type this step-by-step:
// Step 1: Create a counter factory
function createCounter(initialValue = 0) {
let count = initialValue;
return {
// Step 2: Add methods that use the closure
get: () => count,
increment: () => {
count++;
return count;
},
decrement: () => {
count--;
return count;
},
reset: () => {
count = initialValue;
return count;
},
};
}
// Step 3: Test your counter
const counter = createCounter(10);
console.log("Start:", counter.get()); // 10
console.log("After increment:", counter.increment()); // 11
console.log("After decrement:", counter.decrement()); // 10
console.log("After reset:", counter.reset()); // 10
// Step 4: Prove each counter is independent
const counter1 = createCounter(0);
const counter2 = createCounter(100);
counter1.increment();
counter2.decrement();
console.log("Counter1:", counter1.get()); // 1
console.log("Counter2:", counter2.get()); // 99
Exercise 2: Event Handler Practice
Practice arrow functions with event handlers (React pattern):
// Step 1: Create a simple "button" simulator
function createButton(text) {
let clickCount = 0;
return {
// Step 2: Arrow function event handlers (React style)
onClick: () => {
clickCount++;
console.log(`${text} clicked ${clickCount} times`);
},
onDoubleClick: () => {
clickCount += 2;
console.log(`${text} double-clicked! Total: ${clickCount}`);
},
// Step 3: Method to simulate getting current state
getClickCount: () => clickCount,
// Step 4: Reset method
reset: () => {
clickCount = 0;
console.log(`${text} reset`);
},
};
}
// Test your button
const saveButton = createButton("Save");
const cancelButton = createButton("Cancel");
// Simulate clicks
saveButton.onClick(); // "Save clicked 1 times"
saveButton.onClick(); // "Save clicked 2 times"
cancelButton.onDoubleClick(); // "Cancel double-clicked! Total: 2"
console.log("Save clicks:", saveButton.getClickCount()); // 2
console.log("Cancel clicks:", cancelButton.getClickCount()); // 2
Exercise 3: Component Props Simulator
Build a mini component that handles props like React:
// Step 1: Create a component factory
function createComponent(name) {
let props = {};
return {
// Step 2: Set props (like React)
setProps: (newProps) => {
props = { ...props, ...newProps }; // Merge like React
console.log(`${name} props updated:`, props);
},
// Step 3: Get specific prop
getProp: (propName) => props[propName],
// Step 4: Render method (simulated)
render: () => {
const title = props.title || "No Title";
const isVisible = props.visible !== false; // Default to true
if (!isVisible) {
console.log(`${name} is hidden`);
return;
}
console.log(`Rendering ${name}: ${title}`);
},
};
}
// Test your component
const header = createComponent("Header");
header.setProps({ title: "Welcome" });
header.render(); // "Rendering Header: Welcome"
header.setProps({ visible: false });
header.render(); // "Header is hidden"
header.setProps({ visible: true, title: "Hello World" });
header.render(); // "Rendering Header: Hello World"
Troubleshooting & Best Practices
Common Function Mistakes
Forgetting to return from arrow functions with blocks
If you don't specify a return, the function returns undefined by default (except in the case of arrow function implicit returns).
// Bad - no return statement
const double = (x) => {
x * 2; // This does nothing!
};
console.log(double(5)); // undefined
// Good - explicit return
const double = (x) => {
return x * 2;
};
console.log(double(5)); // 10
// Or use implicit return
const double = (x) => x * 2;
console.log(double(5)); // 10
Best Practices
- Functions should focus on doing just one job. It's fine a function to call on another, but it shouldn't do too much work itself.
- Use verb phrases for function names (e.g.,
getUser,setName) to make their purpose clear. - Use noun phrases for variable names (e.g.,
user,name) to make their role obvious. - For function components, use PascalCase noun phrases.
- Generally, follow this structure in your code:
- Imports
- Constants
- Functions
- Component Logic
Wrap-Up & Assessment
Challenge: Build Your Own "useState" Hook
Create a simple version of React's useState using closures:
// Your mission: Complete this useState implementation
function createUseState() {
let stateValue; // This will hold our state
return function useState(initialValue) {
// Set initial value only if stateValue is undefined
if (stateValue === undefined) {
stateValue = initialValue;
}
// Return current state and setter function
const setState = (newValue) => {
stateValue = newValue;
console.log(`State updated to:`, stateValue);
};
return [stateValue, setState]; // Return array like React
};
}
// Test your implementation
const useState = createUseState();
// Simulate a React component using your hook
function simulateComponent() {
const [count, setCount] = useState(0);
console.log(`Current count: ${count}`);
// Simulate button clicks
const handleIncrement = () => setCount(count + 1);
const handleDecrement = () => setCount(count - 1);
return { handleIncrement, handleDecrement };
}
// Test it
const component = simulateComponent();
component.handleIncrement(); // State updated to: 1
component.handleIncrement(); // State updated to: 2
component.handleDecrement(); // State updated to: 1
Hands-On Project: Theme Manager
Build a theme manager using closures and arrow functions:
// Challenge: Complete this theme manager
function createThemeManager(defaultTheme = "light") {
let currentTheme = defaultTheme;
const listeners = [];
return {
// Get current theme
getTheme: () => currentTheme,
// Set new theme and notify listeners
setTheme: (newTheme) => {
const oldTheme = currentTheme;
currentTheme = newTheme;
// Notify all listeners (like React components)
listeners.forEach((listener) => listener(newTheme, oldTheme));
},
// Subscribe to theme changes (like useEffect)
subscribe: (callback) => {
listeners.push(callback);
// Return unsubscribe function (React pattern)
return () => {
const index = listeners.indexOf(callback);
if (index > -1) listeners.splice(index, 1);
};
},
// Toggle between light/dark
toggle: () => {
const newTheme = currentTheme === "light" ? "dark" : "light";
// Use the setTheme method to ensure listeners are notified
// Your code here...
},
};
}
// Test your theme manager
const themeManager = createThemeManager();
// Subscribe to changes
const unsubscribe1 = themeManager.subscribe((newTheme, oldTheme) => {
console.log(`Header: Theme changed from ${oldTheme} to ${newTheme}`);
});
const unsubscribe2 = themeManager.subscribe((newTheme) => {
console.log(`Sidebar: Now using ${newTheme} theme`);
});
// Test the functionality
themeManager.setTheme("dark"); // Both listeners should fire
themeManager.toggle(); // Should go back to light
themeManager.toggle(); // Should go to dark again
// Unsubscribe one listener
unsubscribe1();
themeManager.setTheme("light"); // Only sidebar listener should fire
Key Takeaways
- Closures power React hooks -
useState,useEffect, and custom hooks all use closures - Arrow functions simplify event handlers - no more
.bind(this)worries - Private state through closures - variables are encapsulated and can't be accessed directly
- Arrow functions inherit 'this' from their enclosing scope - crucial for React event handlers
- Closures capture outer variables - the foundation of React hooks and state management
- Functions are first-class citizens - can be stored, passed, and returned like any value
- Hoisting affects function declarations but not expressions or arrow functions
- Modern parameter features (destructuring, defaults, rest) make React props handling cleaner