JavaScript Refresher for React Beginners – Key JS Concepts to Know
JavaScript Refresher for React Beginners – Key JS Concepts to Know 관련
The Back Story
A few years ago, I was introduced to React and immediately fell in love with its component-based, state-driven approach to building web applications.
But as I delved deeper into its ecosystem, I encountered not just React, but a range of supporting libraries like Material UI, React Router, Reactstrap, Redux, and more. While exciting, these new concepts and libraries could also feel overwhelming.
I soon realized that mastering React requires a solid understanding of modern JavaScript, especially the ES6+ features. This realization encouraged me to revisit some fundamental JavaScript topics, which helped me become more comfortable with React and write cleaner, more efficient code.
In this guide, I will share my notes as a concise and practical reference. These key JavaScript concepts will give you a strong foundation before you dive deep into React. Whether you’re a beginner or revisiting the language, this guide should give you a boost as you tackle the React ecosystem.
Let’s Get Back to Work
Since React is based on JavaScript, it’s essential to have a good grasp of the language before you start learning React.
I recommend a comprehensive resource like The Modern JavaScript Tutorial for an in-depth review. But if you feel confident about most of JavaScript and just need a brush-up, here’s my recommended list of crucial topics:
1. Template Literals
Template literals simplify string interpolation and multiline text formatting. By using backticks (`___`
), you can embed expressions into strings with ${}
. This makes concatenating variables and expressions with text easy, eliminating the need for cumbersome string concatenation.
let numEggs = 4;
console.log(`In breakfast, I eat ${numEggs} eggs.`);
//Output: In breakfast, I eat 4 eggs.
console.log(`Today, I ate only half of it, I ate just ${numEggs/2} eggs.`);
//Output: Today I ate only half of it, I ate just 2 eggs.
Additionally, template literals support multiline strings (without needing the newline character, that is \n
, and also you can add spaces). This lets developers create more readable and organized code.
console.log(`Today I had:
- Breakfast
- Lunch
- Dinner`);
/* Output (now shows multiline text as shown below):
Today I had:
- Breakfast
- Lunch
- Dinner
*/
With their flexibility and clarity, template literals have become a preferred method for handling strings and dynamic content in modern JavaScript.
2. Arrow Functions
Arrow functions provide a more concise syntax for writing functions and automatically bind this
to the context in which they're declared. They are a staple of React development, as they simplify event handlers, lifecycle methods, and other functional logic. Let’s explore different variations of arrow functions.
Arrow Function with a Single Argument
When an arrow function has a single argument, you can omit the parentheses. Here’s an example:
const greet = name => `Hello, ${name}!`;
console.log(greet('John')); // Hello, John!
Arrow Function without Arguments
If there are no arguments, you still need to use parentheses.
const sayHello = () => 'Hello, world!';
console.log(sayHello()); // Hello, world!
Arrow Function with Multiple Arguments
For multiple arguments, parentheses are mandatory.
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5
Single-Line Function Body (Implicit Return)
When the function body is a single expression, you can omit the return
keyword and curly braces. This is known as an implicit return.
const multiply = (x, y) => x * y;
console.log(multiply(4, 5)); // 20
Multi-Line Function Body
For more complex logic or multiple statements, you need curly braces, and an explicit return
is required if the function returns a value.
const getFullName = (firstName, lastName) => {
const fullName = `${firstName} ${lastName}`;
return fullName;
};
console.log(getFullName('John', 'Doe')); // John Doe
Returning an Object
To return an object directly, wrap the object in parentheses to avoid confusion with the function body.
const createUser = (name, age) => ({ name, age });
console.log(createUser('Alice', 30)); // { name: 'Alice', age: 30 }
Arrow Functions in Callbacks
Arrow functions are often used as callbacks for array methods like map
, filter
, and reduce
.
const numbers = [1, 2, 3, 4];
const squares = numbers.map(num => num * num);
console.log(squares); // [1, 4, 9, 16]
To learn more about how arrow functions compare with other ways to define functions, you may read this blog on ways to write JS functions.
3. Default Parameters (or Values)
Default parameters allow functions to have pre-set values if no arguments are passed, or when an argument is undefined
. This feature is helpful when writing flexible components in React, where you may not always pass every prop or argument.
function greet(name = 'Stranger') {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, Stranger!
greet('Alice'); // Hello, Alice!
It is relevant to mention here that there’s another commonly used approach in JavaScript, which is leveraging logical operators like ||
to set a default value when the given value is falsy (that is, values like 0
, null
, undefined
, false
, or ""
).
function greet(name) {
const finalName = name || 'Stranger';
console.log(`Hello, ${finalName}!`);
}
greet(); // Hello, Stranger!
greet(''); // Hello, Stranger!
greet('John'); // Hello, John!
The above process uses a concept called short-circuiting of logical operators, which is discussed in Section 9 below.
4. Destructuring Objects and Arrays
Destructuring allows you to extract values from arrays and properties from objects into variables. This concise syntax makes your code cleaner and more readable.
To extract specific values from an array or object, use destructuring by enclosing the desired variables within curly braces for objects or square brackets for arrays.
// Example of Array Destructuring
const [first, second] = [10, 20];
console.log(first); // 10
// Example of Object Destructuring
const user = { name: 'Alice', age: 25 };
const { name, age } = user;
console.log(name); // Alice
Destructuring is commonly used in React for handling props and state. To see more specific use cases of destructuring with code examples, read this quick reference on destructuring.
5. Rest and Spread Operators
The rest and spread operators are incredibly versatile and widely used in JavaScript. Both are represented by three dots (...
), but their meanings differ depending on the context in which they are used.
Spread Operator: Expands elements of an array or object
The spread operator is primarily used to unpack arrays or objects into individual elements. This is especially useful for creating shallow copies or merging arrays (and objects) without mutating the original.
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2); // [1, 2, 3, 4, 5]
/* arr2 is created by spreading the elements of arr1
and then adding additional values */
The spread operator can also be used to copy objects or combine objects:
const obj1 = { name: 'Alice', age: 25 };
const obj2 = { ...obj1, job: 'Engineer' };
console.log(obj2); // { name: 'Alice', age: 25, job: 'Engineer' }
/* obj2 is created by spreading the properties of obj1
and adding a new property */
This is a common pattern when updating the state in React applications.
Rest Operator: Collects multiple elements into an array
The rest operator does the reverse: it collects multiple arguments or elements into a single array. It’s especially useful when working with variadic functions (functions that accept a variable number of arguments).
function sum(...args) {
return args.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
In this example, ...args
gathers all arguments passed to the function into an array. This allows the function to handle any number of arguments dynamically.
You can also use the rest operator to collect remaining properties of an object or elements of an array:
const [first, ...rest] = [10, 20, 30, 40];
console.log(first); // 10
console.log(rest); // [20, 30, 40]
This technique is particularly useful in React when working with props, where you might want to extract specific properties and pass the rest down to child components.
6. Map, Filter, and Reduce Methods
The map()
, filter()
, and reduce()
methods are incredibly powerful for array manipulation in JavaScript, and they play a crucial role in React for tasks like rendering lists, filtering data, and summarizing information.
- Map: Transforms elements of an array
- Filter: Creates a new array with elements that pass a condition
- Reduce: Accumulates values into a single result
1. Map Method
The map()
method creates a new array by transforming each element of the original array according to the function provided. This method is essential in React for dynamically rendering lists of components from arrays of data.
// Example (Basic Usage):
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8]
In React, map()
is commonly used to render lists of components. Here's how you might use it in a React component:
// Example (Rendering a List in React):
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
function UserList() {
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
/* Note that each <li> element in the list has a unique 'key' prop.
It is required by React when rendering lists like this */
2. Filter Method
The filter()
method creates a new array with all elements that pass the condition specified in the callback function. It's frequently used in React when you want to display only certain items based on user input or a specific condition.
// Example (Basic Usage):
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
For a use case, imagine you have a list where some tasks are completed while some are not. So, you can use filter()
to display (render) only the tasks that are not yet completed.
// Example (Filtering Data in React):
const todos = [
{ id: 1, task: 'Learn JavaScript', completed: true },
{ id: 2, task: 'Learn React', completed: false },
{ id: 3, task: 'Build a project', completed: false }
];
function TodoList() {
const incompleteTodos = todos.filter(todo => !todo.completed);
return (
<ul>
{incompleteTodos.map(todo => (
<li key={todo.id}>{todo.task}</li>
))}
</ul>
);
}
3. Reduce Method
The reduce()
method executes a reducer function on each element of the array, resulting in a single output value. It's used when you need to accumulate data, such as summing values or combining objects.
// Example (Basic Usage):
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((total, num) => total + num, 0);
console.log(sum); // 10
You might use reduce()
in React to calculate a summary of some data, like the total price of items in a shopping cart — considering the items’ individual price and quantities:
// Example (Using reduce() in React):
const cart = [
{ id: 1, name: 'Apple', price: 1.5, quantity: 3 },
{ id: 2, name: 'Banana', price: 1, quantity: 2 },
{ id: 3, name: 'Orange', price: 2, quantity: 1 }
];
function CartSummary() {
const total=cart.reduce((sum, item)=>sum + item.price * item.quantity, 0);
return (
<div>
<h3>Total: ${total}</h3>
</div>
);
}
These array methods are invaluable in React for manipulating and displaying data dynamically. They allow for clean, readable, and declarative logic within your components.
You can also combine these methods to create powerful data transformations. For example, you can filter an array and then map the results: filter()
is used to extract only the adult users, and map()
is then used to create a list of their names.
// Example: combined use of filter and map methods
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 17 },
{ id: 3, name: 'Charlie', age: 30 }
];
const adultNames = users
.filter(user => user.age >= 18)
.map(user => user.name);
console.log(adultNames); // ['Alice', 'Charlie']
The map()
and filter()
methods are easier to grasp because they follow a simple "one-to-one" or "one-to-zero-or-more" relationship: map()
transforms each element into a new one, while filter()
includes or excludes elements based on a condition, returning a similarly structured array.
In contrast, reduce()
is more complex, as it accumulates values into a single result rather than maintaining a one-to-one relationship. It requires managing both the current value and an accumulator, making it conceptually different and harder to interpret than map()
or filter()
.
The optional initial value in reduce()
adds to the complexity, as it sets the starting point for the accumulation. Without it, the first array element is used as the accumulator, which can cause unexpected results with empty arrays or non-numeric data. To get consistent results for your use of the reduce()
method, you may read about the importance of an initial value.
7. Mutability Issues with Methods like Array Sort
The Array.sort()
method sorts the elements of an array in place, meaning it mutates the original array. For example:
const numbers = [4, 2, 3, 1];
numbers.sort();
console.log(numbers); // [1, 2, 3, 4]
While this works in plain JavaScript, it’s problematic in React, where immutability is crucial for correctly managing state.
React detects state changes by comparing previous and new states and triggers re-renders based on those changes. But when an array is mutated in place (like with sort()
), React may fail to recognize the change, leading to stale UI updates or unpredictable behavior.
To avoid this, it’s best to create a copy using the spread operator (...
) or slice()
before sorting, ensuring that the original state remains unchanged:
const numbers = [4, 2, 3, 1];
const sortedNumbers = [...numbers].sort();
console.log(sortedNumbers); // [1, 2, 3, 4]
Methods like map()
, filter()
, reduce()
, or copying arrays/objects before modifying them is essential for preserving immutability and ensuring reliable state management in React.
8. Ternary Operator
The ternary operator is a shorthand for conditional statements. It has the syntax condition ? expressionIfTrue : expressionIfFalse
. If the condition evaluates to true, it executes the expressionIfTrue
. If it’s false, it executes the expressionIfFalse
.
let isUserRegistered = true;
let message = isUserRegistered ? 'Please login' : 'Please Sign-up';
console.log(message);
//Output: Please login
In React, it is an efficient replacement for the if-else statements in certain scenarios — like conditional rendering, which delivers elements and components based on certain conditions or values of state or props data.
9. Short-Circuiting and Logical Operators
Logical operators like &&
(AND) and ||
(OR) can be used to create clean and concise conditional logic in JavaScript. In React, these operators often determine whether a component or JSX element should render.
1. Logical AND (&&
)
The &&
operator evaluates the expression on the left side first. If it's true
, the right-hand expression is evaluated and returned. If the left side is false
, the entire expression short-circuits, meaning the right-hand expression is ignored.
let isLoggedIn = true;
console.log(isLoggedIn && 'Welcome back!'); // Welcome back!
This behavior is often used in React for conditional rendering, like so:
function UserGreeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn && <p>Welcome back!</p>}
</div>
);
}
This is a common pattern in React for rendering UI based on certain conditions without needing an explicit if
statement.
2. Logical OR (||
)
The ||
operator works in the opposite way. It evaluates the left-hand expression first, and if it's true
(or any truthy value), it returns that value. If the left-hand expression is false
(or any falsy value), it evaluates and returns the right-hand expression.
let username = '';
console.log(username || 'Guest'); // Output: Guest
In React, this is useful for providing default values, like so:
function UserProfile({ username }) {
return (
<div>
<p>Hello, {username || 'Guest'}!</p>
</div>
);
}
Note that the nullish coalescing operator (??
) is similar to the logical OR operator (||
), but with a key difference in how they treat falsy values.
The nullish coalescing operator (??
) specifically checks if the left-hand side is either null
or undefined
. If the value is null
or undefined
, it returns the right-hand side. This allows 0
, false
, and ''
to be treated as valid values and not overridden. You can read about this and more such nuances associated with JavaScript Operators.
3. Combining &&
and ||
for Complex Logic
You can also combine &&
and ||
to create more complex conditional logic or nested logic.
let isAdmin = false;
let isLoggedIn = true;
console.log(isAdmin && 'Admin Panel' || isLoggedIn && 'User Dashboard');
// Output: User Dashboard
This can be useful in React for deciding what to render based on multiple conditions having an interplay.
function Dashboard({ isAdmin, isLoggedIn }) {
return (
<div>
{isAdmin && <p>Welcome to the Admin Panel</p>}
{!isAdmin && isLoggedIn && <p>Welcome to the User Dashboard</p>}
</div>
);
}
10. Optional Chaining
Optional chaining (?.
) is a powerful feature introduced in JavaScript (ES2020) that allows you to safely access deeply nested properties of an object without worrying about encountering undefined
or null
errors.
In traditional JavaScript, accessing nested properties of objects could result in errors if any part of the chain was undefined
or null
, causing your code to break. Optional chaining provides a cleaner and safer way to handle these scenarios.
The optional chaining operator short-circuits and returns undefined
if the value before it is null
or undefined
. This prevents the code from throwing an error when trying to access the properties of a null
or undefined
value.
let user = { name: 'Alice', address: { city: 'Wonderland' } };
console.log(user?.address?.city);
// Output: Wonderland
// Without optional chaining
let user1 = { name: 'Bill' };
console.log(user1.address.city);
// Output: Error: Cannot read property 'city' of undefined
// With Optional Chaining:
let user2 = { name: 'Caleb' };
console.log(user2?.address?.city); // undefined
Optional chaining can also be used in different scenarios, such as with function calls (to check if a function exists before invoking it), while accessing elements in arrays (especially when you’re unsure whether the array exists or whether it has enough elements), or while accessing dynamic properties, as shown below:
// Optional Chaining with Functions:
let user1 = {
name: 'Alice',
greet: () => 'Hello!'
};
console.log(user1.greet?.()); // Hello!
console.log(user1.sayGoodbye?.()); // undefined
// Optional Chaining with Arrays:
let users = [{ name: 'Alice' }, { name: 'Bob' }];
console.log(users[0]?.name); // Alice
console.log(users[2]?.name); // undefined
// Optional Chaining with Dynamic Properties:
let user2 = { name: 'Bill', preferences: { theme: 'dark' } };
let property = 'preferences';
console.log(user2?.[property]?.theme); // dark
property = 'settings';
console.log(user2?.[property]?.theme); // undefined
When working with deeply nested objects, optional chaining can save you from having to write repetitive null checks at every level.
let user = { profile: { address: { city: 'Wonderland' } } };
// usage without optional chaining (using short-circuiting):
if (user && user.profile && user.profile.address && user.profile.address.city) {
console.log(user.profile.address.city);
}
// usage with optional chaining (saving repititive null checks):
console.log(user?.profile?.address?.city); // Wonderland
In React, optional chaining is particularly useful when dealing with props, API responses, or any data that may not always be available. It helps prevent errors when rendering components based on dynamic or incomplete data.
Optional chaining significantly reduces the complexity of your code, making it cleaner and more readable, especially when dealing with deeply nested structures.
// Example usage of Optional Chaining in React
function UserProfile({ user }) {
return (
<div>
<p>Name: {user?.name}</p>
<p>City: {user?.address?.city ?? 'Unknown'}</p>
</div>
);
}
11. Asynchronous JS: Callbacks, Promises, Async/Await
JavaScript is a single-threaded language, meaning it can execute one task at a time. But handling asynchronous operations is crucial, especially for tasks like fetching data in React. Callback
functions are one of the earliest patterns used to handle asynchronous behavior, like so:
function fetchData(callback) {
setTimeout(() => {
console.log("Data fetched");
callback({ user: "John", age: 30 });
}, 2000);
}
fetchData((data) => {
console.log("User:", data.user);
});
/* Output:
Data fetched
User: John
*/
So, as you can see, callbacks are effective for handling simple operations that depend on asynchronous tasks, like the example above.
But when multiple asynchronous tasks rely on each other, callbacks can lead to deeply nested code, commonly referred to as callback hell.
To solve this problem, promise
s were introduced. A promise is an object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Instead of nesting multiple callbacks, promises allow chaining, leading to more structured and readable code.
Similarly, the Fetch API, which is commonly used for handling network requests in React applications, returns a promise which you can handle like so:
function fetchUserDetails(userId) {
return fetch(`https://api.example.com/users/${userId}`)
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch user details');
}
return response.json();
})
.then((data) => {
console.log("Fetched user details");
return { id: userId, name: data.name };
});
}
function fetchPostsByUser(user) {
return fetch(`https://api.example.com/users/${user.id}/posts`)
.then((response) => {
if (!response.ok) {
throw new Error('Failed to fetch posts');
}
return response.json();
})
.then((posts) => {
console.log(`Fetched posts for ${user.name}`);
return posts;
});
}
// Chain promises
fetchUserDetails(1)
.then((user) => fetchPostsByUser(user))
.then((posts) => console.log(posts))
.catch((error) => console.error("Error:", error));
Promises provide a more readable way to handle sequential asynchronous tasks. They also simplify error handling using .catch()
. But, while promises eliminate the deep nesting of callbacks, chaining too many .then()
calls can still become verbose and hard to follow.
Introduced in ES2017, async/await
makes working with promises even simpler. With async/await, asynchronous code looks and behaves more like synchronous code, which greatly improves readability. Here’s how it works:
- async function: An
async
function returns a promise. Theasync
keyword allows the function to return a resolved promise implicitly. - await expression: Inside an
async
function,await
pauses the execution of the function until the promise resolves. It simplifies promise handling, as we can directly assign the resolved value to a variable.
async function getUserAndPosts(userId) {
try {
const user = await fetchUserDetails(userId); // Waits for user details
const posts = await fetchPostsByUser(user); // Waits for posts
console.log("Posts:", posts);
} catch (error) {
console.error("Error:", error);
}
}
getUserAndPosts(1);
/* Output:
Fetched user details
Fetched posts for John
Posts: ["Post 1", "Post 2"]
*/
Async/await
makes asynchronous code appear synchronous, which greatly improves readability and maintainability. The try/catch
block also simplifies error handling, making it consistent with how errors are caught in synchronous code.
Error Handling in Async Code
Error handling in asynchronous code can be tricky. Callbacks require error-first handling, while promises and async/await
offer more structured approaches, as shown below:
// Handling Errors with Promises
fetchUserDetails(1)
.then((user) => fetchPostsByUser(user))
.then((posts) => console.log(posts))
.catch((error) => console.error("Error fetching data:", error));
// Handling Errors with Async/Await
async function getUserAndPosts() {
try {
const user = await fetchUserDetails(1);
const posts = await fetchPostsByUser(user);
console.log(posts);
} catch (error) {
console.error("Error fetching data:", error);
}
}
Understanding the evolution of asynchronous JavaScript helps you write efficient, non-blocking code. For more details, you can read this article on the evolutionary progress of Asynchronous JavaScript.
12. Closures
A closure in JavaScript is created when a function “remembers” the variables from its outer scope, even after the outer function has finished executing. This means that a function retains access to the environment it was created in, making closures essential for managing data across different contexts.
function outerFunction(outerVar) {
return function innerFunction(innerVar) {
console.log(`Outer: ${outerVar}, Inner: ${innerVar}`);
};
}
const newFunction = outerFunction('React');
newFunction('JavaScript'); // Outer: React, Inner: JavaScript
In React, closures are crucial for handling state and props within functional components. They allow functions like event handlers or asynchronous callbacks to access the latest state values even after re-renders.
For example, the useState
and useEffect
hooks rely on closures to "remember" and manage state across renders.
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1); // Closure keeps track of count
return <button onClick={increment}>Count: {count}</button>;
}
Understanding closures ensures that you can effectively manage state and handle events in React, keeping your components consistent and predictable.
13. Modules (import/export)
React projects are highly modular, meaning that the code is split into multiple files or components, each handling a specific responsibility. This modularity is enabled by ES6 modules, which allow you to export values, functions, or components from one file and import them into another.
Understanding how to use import
and export
is essential for organizing React applications and making code reusable and maintainable.
In the following example, the greet
function is exported from module.js
, making it accessible to other files.
// module.js
export const greet = () => console.log('Hello');
In anotherFile.js
, we import the greet
function from module.js
and can now use it as if it were defined locally, like so:
// anotherFile.js
import { greet } from './module';
greet(); // Hello
React components are often exported from their own files and then imported into a central component (like App.js
), allowing you to break down your UI into smaller, reusable pieces.
// Button.js
export default function Button() {
return <button>Click me</button>;
}
// App.js
import Button from './Button';
Understanding this import/export structure is fundamental in React for managing components, reusing logic, and keeping code clean and modular.
Here, you may note that CommonJS was a popular module system for Node.js (server-side JavaScript) before ES6. It allowed you to export values from a file using module.exports
and import them using require()
.
While it worked well in Node.js, it wasn't natively supported by browsers. With the rise of React and other frontend libraries, the need for a native, browser-supported and standardized module system became essential and ES6 provided that.
14. Event Handling and Bubbling
React relies heavily on event handling to respond to user interactions. Events in React are managed through Synthetic Events, which provide cross-browser consistency and performance optimization. Understanding event bubbling, a process where events propagate from the target element up through its parents, is crucial for controlling component behavior.
// Example in vanilla JavaScript:
document.querySelector("button").addEventListener("click", function() {
console.log("Button clicked");
});
In the above example, clicking the button triggers the event listener, and the event “bubbles” up through parent elements unless stopped. React handles this similarly with its onClick
events:
// Example in React:
function handleClick() {
console.log("Button clicked");
}
function App() {
return <button onClick={handleClick}>Click me</button>;
}
In React, event bubbling can lead to multiple event handlers being triggered in nested elements. For example, a parent’s onClick
can be triggered when its child button is clicked unless it is prevented by calling event.stopPropagation()
inside the child button’s handler method — which then prevents the click event from reaching the parent div
. This ensures that only the desired event is handled.
// Example of How to Stop Event Bubbling:
function handleButtonClick(event) {
event.stopPropagation(); // Prevents bubbling
console.log("Button clicked");
}
function handleDivClick() {
console.log("Div clicked");
}
function App() {
return (
<div onClick={handleDivClick}>
<button onClick={handleButtonClick}>Click me</button>
</div>
);
}
React’s architecture, with its component-based structure and Synthetic Event System, reduces the need for stopPropagation()
in most cases—for example, in simpler apps where both a parent and child component are not handling the same event like a click
.
But in more complex UI structures, where multiple elements handle the same event (for example, onClick
), and you want to prevent the parent element from reacting to the child’s event, stopPropagation()
becomes crucial for controlling event flow. This is especially important in scenarios like nested modals, dropdowns, or accordions, where a click inside the modal should not trigger a click handler on the outer container.
15. Classes and Prototypes
While functional components are now dominant in React, understanding JavaScript classes and prototypes is still valuable, especially when working with class-based components or maintaining legacy code. JavaScript classes provide a blueprint for creating objects, and they work by leveraging prototypes under the hood.
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
const person = new Person('Alice');
person.greet(); // Hello, I'm Alice
In this example, the Person
class defines a constructor to initialize the name
property, and a greet()
method that prints a message. When you create a new instance of Person
, the method is available through JavaScript’s prototype chain.
Though React moved towards functional components with hooks, class-based components were the standard prior to React 16.8. Understanding classes is useful when dealing with codebases that use class components or when you need to understand features like lifecycle methods (componentDidMount
, componentDidUpdate
, and so on) and this
binding, which are more prevalent in class components.
Final Thoughts
Mastering the key JavaScript concepts outlined in this tutorial will give you a solid foundation as you dive into React development.
React heavily relies on modern JavaScript features like map()
, filter()
, destructuring, and the spread operator, all of which streamline how data is manipulated and components are rendered. And concepts like immutability, optional chaining, and the nullish coalescing operator are critical for writing clean, bug-free code that interacts effectively with dynamic data.
By understanding how these JavaScript features work together, you'll be well-equipped to write more efficient, maintainable React applications.
So, as you begin your React journey, make sure your JavaScript fundamentals are rock-solid—you'll find that it pays off as you tackle more complex challenges in your React projects. Also, if you find I missed any important concept here, please let me know. I will add it back to the article for an updated version.