Variables and Types - React Prep Review
Introduction
From JavaScript Review to React Readiness
You've learned JavaScript fundamentals, but React development requires thinking about data differently. This lesson bridges that gap by focusing on the JavaScript concepts you'll use constantly in React: variable declarations, data types, and how JavaScript handles references vs values.
Real-world scenario: In React, you'll manage component state, pass props between components, and handle user interactions. Understanding how JavaScript treats different data types isn't academic—it prevents bugs like state not updating or props not rendering correctly.
What You'll Master Today
- Modern variable declarations and when each matters in React
- The five data types you'll work with daily in React development
- Reference vs value behavior that affects state management
- Modern operators that make React code safer and cleaner
- Why "everything is an object" matters for component development
Open your browser's Developer Console (F12 → Console tab). We'll test concepts immediately.
Core Concept Overview
Variable Declarations: The React Developer's Foundation
React development follows modern JavaScript patterns. Here's what you need to know:
// React component state - always const
const [user, setUser] = useState(null);
const [count, setCount] = useState(0);
// Component props - always const
const UserCard = (props) => {
const { name, email, isActive } = props; // Destructuring - always const
// Only use let for values that must be reassigned
let displayMessage;
if (isActive) {
displayMessage = "User is online";
} else {
displayMessage = "User is offline";
}
return <div>{displayMessage}</div>;
};
The Rule: Use const by default. Only use let when you must reassign the variable itself. Never use var in modern React development.
The Five Data Types That Matter in React
React components work with five essential data types. Let's see them in action:
// Component data you'll handle constantly
const userName = "Sarah Kim"; // string - user input, API responses
const userAge = 28; // number - calculations, IDs, counts
const isLoggedIn = true; // boolean - conditional rendering
const profilePicture = null; // null - intentional "no value"
let notificationCount; // undefined - uninitialized state
// Check their types (rarely needed in React, but good to understand)
console.log(typeof userName); // "string"
console.log(typeof userAge); // "number"
console.log(typeof isLoggedIn); // "boolean"
console.log(typeof profilePicture); // "object" (JavaScript's oldest bug!)
console.log(typeof notificationCount); // "undefined"
Everything Is an Object (Except Primitives)
When you use React methods and JavaScript features, you're working with objects that inherit from prototypes.
You'll often hear the phrase: "Everything in JavaScript is an object, except primitives. And even those are wrapped in objects when needed."
What this means in practice:
- Primitives (string, number, boolean, null, undefined, symbol, bigint) are the simplest values and are stored directly.
- When you call a method on a primitive (like
"hello".toUpperCase()), JavaScript temporarily wraps it in aString/Number/Booleanobject so the method call can work, then returns a new primitive and discards the wrapper.
const message = "hello"; // primitive string
const upper = message.toUpperCase(); // temporary String object wrapper
console.log(message); // "hello" - original primitive
console.log(upper); // "HELLO" - new primitive
When you use React methods and JavaScript features, you're working with objects that inherit from prototypes:
// Primitives (simple values)
const message = "Hello";
const score = 100;
const isReady = true;
// Objects (everything else)
const user = { name: "Alex", age: 25 }; // Plain object
const items = ["apple", "banana"]; // Array (inherits from Object)
const getCurrentTime = () => new Date(); // Function (also an object!)
const birthday = new Date("2000-01-01"); // Date object
// The inheritance chain - everything connects to Object
console.log(items.toString()); // Array inherited this from Object
console.log(birthday.valueOf()); // Date inherited this from Object
Why this matters in React: When you use array methods like map() or filter(), you're calling methods that exist because arrays inherit from Object. Understanding this helps you debug when methods aren't available.
Reference vs Value: The React State Management Game-Changer
This concept prevents more React bugs than any other JavaScript knowledge:
// Primitives: Copied by VALUE
const originalScore = 100;
const newScore = originalScore; // Creates a copy
// Changing newScore doesn't affect originalScore
// Objects and Arrays: Copied by REFERENCE
const originalUser = { name: "Alex", score: 100 };
const userReference = originalUser; // Points to same object!
userReference.score = 150; // Changes the original!
console.log(originalUser.score); // 150 - both changed!
// React state requires new references for updates
const [user, setUser] = useState({ name: "Alex", score: 100 });
// ❌ This won't trigger a re-render (same reference)
const updateUser = () => {
user.score = 150;
setUser(user); // React sees same object, no update
};
// ✅ This will trigger a re-render (new reference)
const updateUserCorrectly = () => {
setUser({ ...user, score: 150 }); // New object with spread operator
};
Hands-On Application
Exercise 1: React State Patterns
Let's practice the reference vs value concept with realistic React scenarios:
// Step 1: Understanding primitive updates (these work fine)
const [count, setCount] = useState(0);
const [message, setMessage] = useState("Hello");
// These create new values, so React sees the change
const incrementCount = () => setCount(count + 1);
const updateMessage = () => setMessage("Hello, React!");
// Step 2: Object state (where developers make mistakes)
const [user, setUser] = useState({
name: "Jordan",
email: "jordan@example.com",
preferences: { theme: "dark", language: "en" }
});
// ❌ WRONG: Mutates existing object (React won't re-render)
const wrongUpdate = () => {
user.name = "Jordan Smith"; // Mutates original
setUser(user); // Same reference, React ignores
};
// ✅ CORRECT: Creates new object (React will re-render)
const correctUpdate = () => {
setUser({ ...user, name: "Jordan Smith" }); // New object
};
// ✅ CORRECT: Updating nested objects
const updateTheme = () => {
setUser({
...user,
preferences: { ...user.preferences, theme: "light" }
});
};
// Test this in your console - set up these functions and try them
Exercise 2: Safe Property Access with Modern Operators
React components often receive props that might be null or undefined. Modern JavaScript operators handle this gracefully:
// Common React scenario: API data might be incomplete
const userProfile = {
name: "Sam Wilson",
contact: {
email: "sam@example.com"
// Note: no phone number provided
}
// Note: no address object
};
// ❌ OLD WAY: Lots of checking
let phoneDisplay;
if (userProfile && userProfile.contact && userProfile.contact.phone) {
phoneDisplay = userProfile.contact.phone;
} else {
phoneDisplay = "No phone provided";
}
// ✅ NEW WAY: Optional chaining and nullish coalescing
const phoneDisplay = userProfile.contact?.phone ?? "No phone provided";
const cityDisplay = userProfile.address?.city ?? "City not specified";
const countryDisplay = userProfile.address?.country ?? "Unknown";
// In React components, this pattern is everywhere
const UserCard = ({ user }) => (
<div>
<h2>{user?.name ?? "Anonymous User"}</h2>
<p>Email: {user?.contact?.email ?? "No email"}</p>
<p>Phone: {user?.contact?.phone ?? "No phone"}</p>
<p>Location: {user?.address?.city ?? "Unknown"}, {user?.address?.country ?? "Unknown"}</p>
</div>
);
Exercise 3: Type Coercion in React
JavaScript's automatic type conversion affects React in predictable ways:
// Common React scenarios where coercion happens
const userInput = "5"; // String from form input
const itemCount = 3; // Number from array length
// Addition vs concatenation
console.log(userInput + itemCount); // "53" - strings concatenate
console.log(Number(userInput) + itemCount); // 8 - proper math
// Boolean context (common in conditional rendering)
const emptyArray = [];
const emptyString = "";
const zeroValue = 0;
// These all evaluate to false in conditions:
console.log(!!emptyArray); // false (empty array is truthy!)
console.log(!!emptyString); // false
console.log(!!zeroValue); // false
// React conditional rendering gotcha:
const items = [];
// ❌ This renders "0" on the page when items is empty
{items.length && <ItemList items={items} />}
// ✅ This renders nothing when items is empty
{items.length > 0 && <ItemList items={items} />}
Advanced Concepts & Comparisons
Modern Operators for Safer React Code
These operators solve common React development problems:
// Nullish coalescing (??) - only null/undefined trigger default
const userName = user?.name ?? "Guest"; // Good
const userName = user?.name || "Guest"; // Problematic with empty strings
// Example: user.name is "" (empty string)
console.log(user?.name ?? "Guest"); // "" (keeps empty string)
console.log(user?.name || "Guest"); // "Guest" (treats empty string as falsy)
// Optional chaining (?.) - safe property access
const userData = {
profile: {
settings: {
notifications: { email: true }
}
}
};
// Safe nested access
const emailNotifications = userData?.profile?.settings?.notifications?.email ?? false;
// Array method chaining with optional chaining
const users = null;
const activeUsers = users?.filter(user => user.isActive)?.length ?? 0;
React-Specific Type Patterns
// Props destructuring with defaults
const Button = ({
text = "Click me", // Default for undefined
onClick = () => {}, // Default function
disabled = false, // Default boolean
variant = "primary" // Default string
}) => (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{text}
</button>
);
// State initialization patterns
const [user, setUser] = useState(null); // Will be object later
const [items, setItems] = useState([]); // Start with empty array
const [loading, setLoading] = useState(false); // Boolean flag
const [error, setError] = useState(null); // Error message or null
Troubleshooting & Best Practices
Common React State Mistakes
// ❌ MISTAKE: Mutating state directly
const [todos, setTodos] = useState([]);
const addTodo = (newTodo) => {
todos.push(newTodo); // Mutates original array
setTodos(todos); // React won't re-render
};
// ✅ SOLUTION: Create new array
const addTodo = (newTodo) => {
setTodos([...todos, newTodo]); // New array with spread
};
// ❌ MISTAKE: Nested object mutation
const [user, setUser] = useState({ profile: { name: "Alex" } });
const updateName = (newName) => {
user.profile.name = newName; // Mutates nested object
setUser(user); // React won't see the change
};
// ✅ SOLUTION: Deep copying with spread
const updateName = (newName) => {
setUser({
...user,
profile: { ...user.profile, name: newName }
});
};
Type Safety Patterns
// Defensive programming for props
const UserProfile = ({ user }) => {
// Guard against missing data
if (!user) {
return <div>Loading user profile...</div>;
}
// Safe access to potentially missing properties
const displayName = user.firstName && user.lastName
? `${user.firstName} ${user.lastName}`
: user.email?.split('@')[0] ?? 'Anonymous';
return (
<div>
<h1>{displayName}</h1>
<p>Email: {user.email ?? 'Not provided'}</p>
<p>Member since: {user.joinDate ?? 'Unknown'}</p>
</div>
);
};
Performance Considerations
// ✅ GOOD: Object references help React optimize
const expensiveData = useMemo(() => {
return processLargeDataset(rawData);
}, [rawData]);
// ✅ GOOD: Function references prevent unnecessary re-renders
const handleClick = useCallback((id) => {
setSelectedItem(id);
}, []);
// ❌ AVOID: Creating new objects in render
const UserCard = ({ user }) => (
<div style={{ padding: 10, margin: 5 }}> {/* New object every render */}
{user.name}
</div>
);
// ✅ BETTER: Stable references
const cardStyles = { padding: 10, margin: 5 };
const UserCard = ({ user }) => (
<div style={cardStyles}>
{user.name}
</div>
);
Wrap-Up & Assessment
Key Takeaways
- Use
constby default - React state and props are typically constant references - Understand reference vs value - Critical for state updates and preventing bugs
- Master modern operators -
?.and??make React code safer and cleaner - Think in terms of new references - React needs new objects/arrays to trigger updates
- Guard against missing data - APIs and user input are unpredictable
React Readiness Check
Test your understanding with these React scenarios:
// Scenario 1: State update
const [user, setUser] = useState({ name: 'Alex', age: 25 });
// How do you update just the age?
// Scenario 2: Conditional rendering
const items = [];
// How do you render a list only when items exist?
// Scenario 3: Safe property access
const user = { profile: null };
// How do you safely get user.profile.email?
// Scenario 4: Props with defaults
// How do you destructure props with fallback values?
Solutions:
user = { ...user, age: 26 }(creates new object)items.length > 0(avoids displaying "0")userData.profile?.email ?? 'No email'(safe access with fallback)const myFunction = ({ title = 'Default', onClick = () => {} } = {}) => ...(destructuring with defaults)
HW: State Management Practice
Create a simple React-style state manager using the concepts you've learned:
// Build this function that mimics useState behavior
const createState = (initialValue) => {
let currentValue = initialValue;
const getValue = () => currentValue;
const setValue = (newValue) => {
// Your task: Handle both direct values and updater functions
// Direct: setValue(5)
// Updater: setValue(prev => prev + 1)
};
return [getValue, setValue];
};
// Test your implementation
const [getCount, setCount] = createState(0);
console.log(getCount()); // 0
setCount(5);
console.log(getCount()); // 5
setCount(prev => prev + 1);
console.log(getCount()); // 6
Next Steps: Functions and Scope
With solid variable and type foundations, you're ready to tackle:
- Function declarations and expressions
- Arrow functions and their role in React
- Scope and closures (how React hooks work internally)
- Higher-order functions (the foundation of React patterns)
The patterns you've learned here - reference vs value, safe property access, and immutable updates - are the building blocks of effective React development.