Mastering JavaScript for React (TypeScript Edition) in 30 Practical Examples
Moving from JavaScript to React can feel confusing, especially when familiar JS features start behaving in nuanced ways inside React components. Fear not! This tutorial is written in a conversational, developer-friendly tone to guide you through 30 practical examples that will help you master the JavaScript (and TypeScript) techniques most often used in React development. We’ll cover everything from modern ES6+ syntax to asynchronous patterns and TypeScript typings, using real-life React examples (with both functional hooks and class components where relevant) so you can quickly apply these concepts with confidence. Let’s dive in and make your transition to React in 2025 a smooth and empowering journey! 1. Arrow Functions for Concise Callbacks Arrow functions provide a concise syntax for writing functions and automatically bind the lexical this context (they don't have their own this context). In React, arrow functions are commonly used for inline event handlers and to avoid explicitly binding methods in class components. This leads to cleaner code and fewer pitfalls with the this keyword. Why it matters in React: Arrow functions let us write event handlers directly in JSX or define class methods without worrying about losing the component context. This means no more this.someMethod = this.someMethod.bind(this) in constructors if you use arrow syntax. Example (Functional & Class): In a functional component, you can define an event handler inline. In a class component, using an arrow function as a method ensures this refers to the class instance. // Functional Component: Arrow function in an onClick handler const CounterButton: React.FC = () => { const [count, setCount] = React.useState(0); return ( setCount(prev => prev + 1)}> Increment Count (current: {count}) ); }; // Class Component: Arrow function method to auto-bind `this` class CounterButtonClass extends React.Component { state = { count: 0 }; // Define handleClick as an arrow property to bind `this` handleClick = () => { this.setState(prev => ({ count: prev.count + 1 })); }; render() { return ( Increment Count (current: {this.state.count}) ); } } In the functional version, the inline arrow function updates state without any extra ceremony. In the class version, handleClick is an arrow function property, so it inherits the class this context automatically. No explicit binding is required. 2. Template Literals for Dynamic Strings Template literals (using ` backticks) allow you to embed expressions into strings easily. This is handy in React for constructing class names, inline styles, or any dynamic text in your JSX. Why it matters in React: You often need to build strings based on props or state (for example, CSS class names or IDs). Template literals make this more readable than string concatenation. Example: Using a template literal to set a dynamic CSS class on a based on a prop value: interface AlertProps { type: 'success' | 'error'; message: string; } const Alert: React.FC = ({ type, message }) => { const className = `alert ${type === 'error' ? 'alert-error' : 'alert-success'}`; return {message}; }; Here we use a template literal `alert ${...}` to include either "alert-error" or "alert-success" depending on the type prop. This is much cleaner than doing a string concat like "alert " + (condition ? ...). Template literals can also span multiple lines and include any JS expression, which comes in handy for formatting text in JSX without ugly concatenations. 3. Object Destructuring (Props and State) Object destructuring lets you extract properties from an object into variables in a single, concise statement. In React, this is commonly used to pull values from props or state for easier use. Why it matters in React: Destructuring props and state makes your component code cleaner by avoiding repetitive this.props.x or props.x references. It also clearly documents which props or state fields are used. Example: Destructuring props in both functional and class components: interface UserBadgeProps { name: string; age: number; } // Functional Component: destructure in the parameter list const UserBadge: React.FC = ({ name, age }) => ( {name} is {age} years old. ); // Class Component: destructure inside render class UserBadgeClass extends React.Component { render() { const { name, age } = this.props; return {name} is {age} years old.; } } In the functional version, we destructure name and age directly in the function signature. In the class version, we destructure inside the render() method. Either way, we can use name and age directly, which is shorter and clearer than writing this.props.name or similar each time. This technique also works for state (e.g., const { something } = this.state in a class, or extracting multiple values from a useState object). 4. Array Destructuring (e.g. Hook

Moving from JavaScript to React can feel confusing, especially when familiar JS features start behaving in nuanced ways inside React components. Fear not! This tutorial is written in a conversational, developer-friendly tone to guide you through 30 practical examples that will help you master the JavaScript (and TypeScript) techniques most often used in React development. We’ll cover everything from modern ES6+ syntax to asynchronous patterns and TypeScript typings, using real-life React examples (with both functional hooks and class components where relevant) so you can quickly apply these concepts with confidence. Let’s dive in and make your transition to React in 2025 a smooth and empowering journey!
1. Arrow Functions for Concise Callbacks
Arrow functions provide a concise syntax for writing functions and automatically bind the lexical this
context (they don't have their own this
context). In React, arrow functions are commonly used for inline event handlers and to avoid explicitly binding methods in class components. This leads to cleaner code and fewer pitfalls with the this
keyword.
Why it matters in React: Arrow functions let us write event handlers directly in JSX or define class methods without worrying about losing the component context. This means no more this.someMethod = this.someMethod.bind(this)
in constructors if you use arrow syntax.
Example (Functional & Class): In a functional component, you can define an event handler inline. In a class component, using an arrow function as a method ensures this
refers to the class instance.
// Functional Component: Arrow function in an onClick handler
const CounterButton: React.FC = () => {
const [count, setCount] = React.useState(0);
return (
<button onClick={() => setCount(prev => prev + 1)}>
Increment Count (current: {count})
button>
);
};
// Class Component: Arrow function method to auto-bind `this`
class CounterButtonClass extends React.Component<{}, { count: number }> {
state = { count: 0 };
// Define handleClick as an arrow property to bind `this`
handleClick = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<button onClick={this.handleClick}>
Increment Count (current: {this.state.count})
button>
);
}
}
In the functional version, the inline arrow function updates state without any extra ceremony. In the class version, handleClick
is an arrow function property, so it inherits the class this
context automatically. No explicit binding is required.
2. Template Literals for Dynamic Strings
Template literals (using `
backticks) allow you to embed expressions into strings easily. This is handy in React for constructing class names, inline styles, or any dynamic text in your JSX.
Why it matters in React: You often need to build strings based on props or state (for example, CSS class names or IDs). Template literals make this more readable than string concatenation.
Example: Using a template literal to set a dynamic CSS class on a Here we use a template literal Template literals can also span multiple lines and include any JS expression, which comes in handy for formatting text in JSX without ugly concatenations.
Object destructuring lets you extract properties from an object into variables in a single, concise statement. In React, this is commonly used to pull values from props or state for easier use.
Why it matters in React: Destructuring props and state makes your component code cleaner by avoiding repetitive Example: Destructuring props in both functional and class components: In the functional version, we destructure This technique also works for state (e.g., Array destructuring is similar to object destructuring, but for array elements. A very common use case in React is with React Hooks like Why it matters in React: Hooks often return a tuple (array) of values. Destructuring lets you name those values clearly. For example, Example: Using array destructuring with the React Hook When calling Another example: if you had a function that returns an array, you could destructure it similarly. In React, you'll mainly see this with Hooks.
The spread operator ( Why it matters in React: Immutability is key in React state updates – you often create new objects/arrays from old ones. Spread syntax makes it easy to copy an array or object with updated values. Also, when you have a set of props to forward to a child, the spread operator helps pass them through.
Example 1 – Copying and updating state: Suppose you have an array in state and want to add an item: Here Example 2 – Spreading props: You can forward props to a child component: In Rest syntax is the sibling of spread. While spread expands iterable/objects, rest collects multiple elements into an array or object. In functions, rest parameters gather all remaining arguments into an array. In object destructuring, rest can gather remaining properties into a new object.
Why it matters in React: Rest parameters can make your functions more flexible by handling an arbitrary number of arguments. In React components, rest props (as shown above) allow your component to take "extra" props without explicitly listing them, which is great for wrapper components.
Example 1 – Rest parameters in a function: Suppose we want a utility to log a message with varying values: The Example 2 – Rest in object destructuring (props): In the The rest operator thus helps in writing flexible components and functions by capturing "the rest" of the data easily.
JavaScript allows function parameters to have default values. If an argument isn’t provided (or is Why it matters in React: Setting default prop values helps your component have sensible behavior even if a parent doesn't pass all props. Using JS default parameters (or defaultProps) prevents Example – Functional component default prop: Here, if Example – Class component default prop: In a class, one way is using the Using default parameters or In JavaScript, Why it matters in React: If you're writing class components, understanding Example: Comparing In If you try to use Understanding this difference helps avoid mistakes like trying to do When using class components, event handler methods that you pass to JSX need the correct Why it matters in React: If you forget to bind a class handler, Example – Manual bind vs arrow property: In this class, By contrast, Today, using arrow function class properties is the common pattern to avoid manual binding and keep code cleaner.
The Why it matters in React: If you have an array of items (say, a list of users or products), you can Example: Rendering a list of strings as list items: Here, You can also map an array of objects to an array of components: This will produce two The Why it matters in React: Sometimes you want to render only items that meet certain criteria. By filtering your data array before mapping to JSX, you can conditionally include or exclude elements in the render.
Example: Filtering a list of products to show only those on sale: First we use This pattern can be combined inline too: The Why it matters in React: If you need to derive a value from a list – such as computing the total price from an array of items, or grouping items by category for separate renders – Example: Calculating and displaying the sum of an array of numbers: Here, Another example – transforming an array to an object map (though this is a bit more advanced use): Here we reduced an array of people into an object where you can lookup by In summary, use The ternary operator ( Why it matters in React: It’s common to show either one UI element or a different one based on state or props. A ternary in JSX allows this without an if/else block (which can’t directly be used inside JSX).
Example: Displaying a login button if the user is not logged in, or a greeting if they are: Here we use Welcome...> : } Without a ternary, you'd have to prepare a variable before the return or use logical && which only handles the "show or nothing" case. Ternary shines when you have two distinct elements to toggle between.
Make sure the expressions on both sides of Short-circuit evaluation using the logical AND ( Why it matters in React: This provides a neat way to say "if this condition is true, then show this part of the UI; if not, show nothing." It avoids an else case entirely.
Example: Only render a loading spinner if a loading flag is true: If This pattern is great for optional sub-components, like modal dialogs ( One caveat: if the left side can sometimes be a number or string Optional chaining ( Why it matters in React: You often receive props or state that could be missing certain nested data (especially when dealing with APIs). Optional chaining allows you to guard against accessing properties of Example: Accessing a deeply nested prop safely: In the first Without optional chaining, we’d need to do a lot of checks: Nullish coalescing ( Why it matters in React: When rendering, you might want to show a default text or value if a prop/state is missing (null/undefined). Example: Providing a default username if none is provided: If Another example: In summary, use JavaScript promises are used to handle asynchronous operations. In a React app, you frequently deal with promises when fetching data or performing any async task (like interacting with an API). Understanding promises lets you coordinate these operations and update your component once the promise settles.
Why it matters in React: React by itself doesn’t change how promises work, but you often use promises within effects or event handlers. Knowing how to chain Example: Fetching data with In this example, we call We placed this in a Promises are often used with the newer async/await syntax (coming next), but it's important to understand the underlying Async/await is syntactic sugar over promises that allows you to write asynchronous code in a synchronous style. By marking a function Why it matters in React: Async/await can make your data fetching or other async operations easier to read and maintain. In React, you'd typically use async/await inside Example: The previous fetch example rewritten with async/await: We define an inner async function Async/await makes the flow of asynchronous logic more intuitive. Just remember that any function using A closure is when a function "remembers" the variables from the place where it was defined, even if it's executed later, potentially in a different scope. In React, closures come into play especially with state and hooks: a function defined in a component render can capture state variables, leading to what’s called “stale state” if not handled correctly.
Why it matters in React: If you use functions inside Example – Stale closure with a timeout: Try this: click "Increment" a few times, then click "Show Alert". The alert will appear 3 seconds later. You might expect it to show the latest count, but often it will show an older value. Why? Because the function passed to This is a closure issue. To fix it, you might use the functional state update or always read latest value via a ref. For example, using a functional update in setTimeout: (not a recommended pattern to access DOM, but illustrates retrieving fresh info at call time), or better, use a ref to always have the latest count.
The key is: any function defined in a render will hold onto the variables from that render. If those variables change later, the old function doesn't magically update them. React's hooks like In summary, closures are powerful (they allow inner functions to access outer scope), but in React they mean you should be mindful that a callback might not “see” updated state unless you account for it.
Using Why it matters in React: Timers are a common JavaScript tool to delay or repeat actions. In a React component, if you start a timer, you should clear it if the component unmounts (or if the effect re-runs and you want to reset the timer). Otherwise, you might attempt to update state on an unmounted component or just waste resources.
Example: Setting up a timer to auto-hide a message: When this component mounts or when If we didn't clear the timeout, and the component was removed (unmounted) quickly, the timeout would still fire and attempt The same principle applies to In React, state should be treated as immutable – you do not want to modify state variables (objects or arrays) in place. Instead, always make a copy and then set state to the new copy. This is because React relies on detecting changes (usually via reference equality) to know when to re-render. If you mutate state directly, you might not trigger a re-render and could cause bugs.
Why it matters in React: Direct mutations (e.g., Example – Incorrect vs correct state update (class): In the incorrect example, we directly push into Example – Functional component with object state: By copying The rule of thumb: treat state as read-only. If you need to change it, produce a new value (object/array) and use that in your state setter. This ensures you don't accidentally override data or miss updates.
React gives two ways to create components: class components and functional components. Understanding their differences is key as you transition to modern React (which favors functional components with Hooks).
Why it matters: Class components use an older API (with Example – Similar component in class and functional form: Both components do the same thing: start at a given count (default 0) and increment on button click, logging when mounted. The class uses Key differences:
In 2025, most new code is written with functional components and hooks, but class components still work and exist in many codebases. It's useful to recognize both patterns.
TypeScript allows us to define the shape of props a component should receive. This makes our components safer and self-documenting. You can use interface or type aliases to describe props, and then apply that to your React component.
Why it matters: With proper prop types, TypeScript will catch when a parent component passes wrong or missing props. It also provides IntelliSense for consumers of your component. This reduces bugs and misunderstandings about what props are expected.
Example – Functional and Class component prop typing: We declared Now if someone uses TypeScript also supports using type aliases instead of interfaces for props (e.g. Just like props, you can type the component’s state. For class components, state typing is done by providing a second generic argument to Why it matters: Typing state ensures that when you call Example – Class component state typing: Here we declared an interface Example – useState typing (functional): In this functional example, we explicitly provided the generic For instance: Typing state in functional components is mostly about the Generics allow components or functions to be type-parametrized. This is useful for creating reusable components or hooks that work with different data types. You might not use generics in every component, but they shine for things like list or form components that can handle various types.
Why it matters: With generics, you can write one component that is flexible with types while still getting full type safety. For example, a list component can accept an array of any type Example – Generic List component: The You could similarly create a generic hook. For example, a hook to filter an array: Here Generics add a bit of complexity, but they empower you to write highly reusable and type-safe components and hooks in a React+TS codebase.
Union types allow a variable (or prop) to be one of several types. A common pattern is a discriminated union, where a prop can have multiple shapes distinguished by a literal field. This is useful in React for making a component that has slightly different props in different modes or variants.
Why it matters: Union types let you model variations in props. Instead of making a prop optional and doing runtime checks, you can use union types to have the compiler enforce that "if prop Example – Discriminated union for an Alert component: Here If someone using Even simpler unions are useful, e.g., a prop that can be one of a few string literals: Now Union and literal types thus help create flexible yet type-safe component interfaces in React.
In TypeScript, DOM events in React have specific types. For example, a form input change event is Why it matters: By typing your event handlers, you get autocomplete for event properties (like Example – Typing an onChange handler for an input: Here, For a button click: A submit event for a form would be You often don't need to annotate these by hand because TS can infer the type from the JSX attribute (if you define handler inline). But if you separate the handler (like in our example), adding the type is a good practice. It documents what element it expects and allows you to use the event object with full type support.
In summary, use the shows an example of defining an Refs provide a way to access DOM nodes or React elements directly. In TypeScript, you should specify what type of element a ref refers to. This avoids the need for casting and ensures you access valid properties on that ref.
Why it matters: If you want to focus an input or measure a div’s size, you'll use refs. Typing them makes sure, for example, that your ref to an Example – Using We initialize For class components, you'd use Typing refs is especially useful when dealing with non-HTML things, like a ref to another React component (you'd use But for common DOM refs, providing the DOM type (HTMLInputElement, HTMLDivElement, etc.) gives you safe access to its properties and methods.
When updating state based on the previous state, it's best to use the functional update form of Why it matters: If you rely on Example – incrementing state multiple times: If we incorrectly did In class components, a similar pattern: If you call that twice (in the same tick), each gets the updated React will batch updates, but apply each functional updater in order with the latest state. This pattern avoids the bug of stale state usage.
Use functional updates whenever your new state depends on the old state. Common cases: counters (as above), toggling booleans ( Modern React code uses ES modules for organizing code into files. Understanding how to export components or utilities and import them elsewhere is fundamental. There are two kinds of exports: named exports and default exports.
Why it matters: In a React project, you'll split components into separate files and use imports/exports to use them. Knowing the syntax helps avoid confusion (for example, when to use curly braces in imports). Also, consistent usage of default vs named exports can improve clarity.
Example – Default export vs named export: Here, If we had a named export instead: We exported two named members. To import them: Notice we use In summary, default export: one per file, import without braces. Named exports: many per file, import with braces. You can mix, but it's often a style choice to either have a default (often for the main component in a file) or only named exports. React components are frequently default-exported (one component per file), but utility modules often have multiple named exports.
Understanding modules ensures you can organize your React code and use components across files. The ES module system has been standard in React apps since the move to build tools like webpack/CRA, and knowing it is essential for any modern JS development.
summarizes: a module can have multiple named exports, but only one default export.
By mastering these 30 JavaScript and TypeScript techniques in the context of React, you should feel more confident in writing clean, efficient, and error-free React code. Each concept, from basic syntax like arrow functions and destructuring to more advanced TypeScript patterns, plays a role in everyday React development. Keep this guide as a quick reference, and happy coding as you build awesome React applications in 2025 and beyond!
interface AlertProps { type: 'success' | 'error'; message: string; }
const Alert: React.FC<AlertProps> = ({ type, message }) => {
const className = `alert ${type === 'error' ? 'alert-error' : 'alert-success'}`;
return <div className={className}>{message}div>;
};
`alert ${...}`
to include either "alert-error"
or "alert-success"
depending on the type
prop. This is much cleaner than doing a string concat like "alert " + (condition ? ...)
.
3. Object Destructuring (Props and State)
this.props.x
or props.x
references. It also clearly documents which props or state fields are used.
interface UserBadgeProps { name: string; age: number; }
// Functional Component: destructure in the parameter list
const UserBadge: React.FC<UserBadgeProps> = ({ name, age }) => (
<p>{name} is {age} years old.p>
);
// Class Component: destructure inside render
class UserBadgeClass extends React.Component<UserBadgeProps> {
render() {
const { name, age } = this.props;
return <p>{name} is {age} years old.p>;
}
}
name
and age
directly in the function signature. In the class version, we destructure inside the render()
method. Either way, we can use name
and age
directly, which is shorter and clearer than writing this.props.name
or similar each time.
const { something } = this.state
in a class, or extracting multiple values from a useState
object).
4. Array Destructuring (e.g. Hook Results)
useState
or useReducer
, which return arrays.
useState
returns [state, setState]
. By destructuring, you assign meaningful names in one step.
useState
:
const [count, setCount] = React.useState<number>(0);
<button onClick={() => setCount(count + 1)}>Increment: {count}button>;
useState(0)
, we get back an array [value, updater]
. The destructuring const [count, setCount] = ...
assigns the first element to count
and the second to setCount
. This pattern is used for all hooks that return arrays (like [state, dispatch] = useReducer(...)
, or [value, refSetter] = useState()
itself).
5. Spread Operator for Props and State Updates
...
) allows you to expand (or spread) iterable elements or object properties. In React, it's widely used for copying state objects/arrays (to avoid mutations) and for passing groups of props to a component.
const [items, setItems] = React.useState<string[]>(['a', 'b']);
const addItem = (item: string) => {
setItems(prevItems => [...prevItems, item]); // create new array with old items + new item
};
[...]
creates a new array containing all of prevItems
plus the new item
at the end. We use the spread operator to avoid mutating the original array.
interface BaseButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
label: string;
}
const BaseButton: React.FC<BaseButtonProps> = ({ label, ...rest }) => (
<button {...rest}>{label}button>
);
// Usage:
<BaseButton label="Click" onClick={() => console.log('clicked')} disabled={isLoading} />
BaseButton
, we destructure label
and then collect the remaining props in ...rest
. The spreads those remaining props onto the real
element. This way,
BaseButton
can accept any standard button props (like onClick
, disabled
, etc.) and pass them through automatically. The spread operator makes this pattern straightforward.
6. Rest Parameters and Props
function logValues(message: string, ...values: number[]) {
console.log(message, values.join(', '));
}
logValues("Scores:", 10, 20, 30);
// console output: "Scores: 10, 20, 30"
...values
in the function definition captures any number of arguments after message
into an array named values
. This is standard JavaScript, but it's useful in React if you write functions that need to handle multiple items (though in React specifically, you often pass arrays instead).
BaseButton
example above, we used { label, ...rest } = props
. That syntax collected all prop fields except label
into the rest
object. This is how you implement components that accept "any other props" to forward to a child.
7. Default Parameters and Default Props
undefined
), the default kicks in. In React, you might want default values for props. With functional components, you can use default parameter syntax or default values in destructuring. For class components, you can use default props or similar patterns.
undefined
errors and makes the component more robust.
interface GreetProps { name?: string; }
const Greet: React.FC<GreetProps> = ({ name = "Guest" }) => (
<h1>Welcome, {name}!h1>
);
name
is not provided by the parent, it defaults to "Guest"
. We achieved this by using name = "Guest"
in the function's parameter list. This is the recommended way to handle default props in modern React/TypeScript, and it keeps the default close to where the prop is used.
interface GreetProps { name?: string; }
class GreetClass extends React.Component<GreetProps> {
static defaultProps = { name: "Guest" };
render() {
const { name } = this.props;
return <h1>Welcome, {name}!h1>;
}
}
defaultProps
static property. Here we declare defaultProps
on the class so that if name
isn’t provided, it defaults to "Guest"
. TypeScript understands this pattern, but note that the React community is moving away from defaultProps
on function components (it may be deprecated) in favor of default parameters.
defaultProps
ensures your component doesn't receive undefined
for missing props, making your code safer and more predictable.
8. The
this
Keyword in React Components
this
refers to the context in which a function is called. In React class components, this
typically refers to the component instance, which holds props, state, and methods. Functional components, on the other hand, don’t use this
at all.
this
is crucial to access this.props
or this.state
. Losing track of this
(for example, in an unbound callback) can cause errors. In functional components, you avoid this
altogether, which is one reason many prefer them.
this
usage in class vs functional:
// Class Component using `this`
class Welcome extends React.Component<{ name: string }> {
render() {
return <p>Hello, {this.props.name} (from class)!p>;
}
}
// Functional Component (no `this`)
const WelcomeFunc: React.FC<{ name: string }> = ({ name }) => {
return <p>Hello, {name} (from function)!p>;
};
Welcome
(class), we access the name via this.props.name
. In WelcomeFunc
, we just use the name
variable (no this
needed).
this
inside a functional component, it won’t be what you expect – there is no component instance. All data for functional components comes through props or hooks, not this
.
this.setState
in a functional component or forgetting to bind methods in classes (which leads to this
being undefined in the method).
9. Binding
this
vs Arrow Functions in Classes
this
context. By default, in JavaScript class methods are not bound to the class instance. There are two common solutions: manually binding them in the constructor, or using arrow functions (as class properties) which automatically use the class this
.
this
will be undefined
when the method is called (because it’s called as a callback without context). This is a classic source of bugs in React classes. Arrow function class properties save you from this by auto-binding.
class Clicker extends React.Component<{}, { clicks: number }> {
state = { clicks: 0 };
// Method that is not bound by default
handleClickBad() {
this.setState({ clicks: this.state.clicks + 1 });
}
// Arrow function method (auto-bound)
handleClickGood = () => {
this.setState(prev => ({ clicks: prev.clicks + 1 }));
};
constructor(props: {}) {
super(props);
// manual binding of handleClickBad
this.handleClickBad = this.handleClickBad.bind(this);
}
render() {
return (
<>
<button onClick={this.handleClickBad}>Bad Click (manual bind)button>
<button onClick={this.handleClickGood}>Good Click (arrow)button>
<p>Total clicks: {this.state.clicks}p>
>
);
}
}
handleClickBad
is a normal method. We bind it in the constructor: this.handleClickBad.bind(this)
. If we didn't, clicking the "Bad Click" button would throw an error because this
would be undefined inside handleClickBad
.
handleClickGood
is defined as an arrow function property. Arrow functions don't have their own this
, they use the surrounding this
(which is the component instance). So it's effectively bound automatically. No constructor code needed. Clicking "Good Click" works without additional binding.
10. Using Array.map to Render Lists
Array.prototype.map
method takes an array and transforms it into a new array by applying a function to each element. In React, map
is indispensable for rendering lists of elements or components from arrays of data.
.map
over it to return an array of JSX elements. This is the idiomatic way to generate repeated elements dynamically. Each item should have a unique key
prop when rendered in a list to help React optimize updates.
const fruits = ['Apple', 'Banana', 'Cherry'];
const FruitList: React.FC = () => {
return (
<ul>
{fruits.map(fruit => (
<li key={fruit}>{fruit}li>
))}
ul>
);
};
fruits.map(...)
takes each fruit name and returns an element. The result is an array of
elements which React renders inside the
. We use key={fruit}
in this simple case (each fruit name is unique). In real life, you might use a unique ID from the data as the key.
interface User { id: number; name: string; }
const users: User[] = [ {id: 1, name: 'Alice'}, {id: 2, name: 'Bob'} ];
<div>
{users.map(user => (
<div key={user.id}>Hello, {user.name}div>
))}
div>
map
method is a powerful tool to translate data into UI elements in React.
11. Using Array.filter for Conditional Lists
Array.prototype.filter
method creates a new array with only the elements that pass a given condition. In React, filter
is useful when you want to display a subset of data based on some condition (perhaps a search query or a toggle).
interface Product { id: number; name: string; onSale: boolean; }
const products: Product[] = [
{ id: 1, name: "Phone", onSale: true },
{ id: 2, name: "Laptop", onSale: false },
{ id: 3, name: "Tablet", onSale: true }
];
const SaleProducts: React.FC = () => {
const saleItems = products.filter(p => p.onSale);
return (
<ul>
{saleItems.map(item => (
<li key={item.id}>{item.name} (On Sale!)li>
))}
ul>
);
};
filter
to get only the products where onSale
is true. Then we map those to elements. The result is that only "Phone" and "Tablet" from the array will be rendered (each with the "(On Sale!)" note).
{products.filter(...).map(...)}
in JSX is valid, but splitting into a variable like saleItems
can improve readability.
12. Using Array.reduce for Derived Data
Array.prototype.reduce
method boils an array down to a single value (or a new array/object) by cumulatively combining elements through a reducer function. In React, you might use reduce
to calculate a summary or aggregate to display, or to transform an array into a different structure needed for rendering.
reduce
can do it elegantly without writing manual loops.
const numbers = [10, 20, 5];
const Total: React.FC = () => {
const total = numbers.reduce((sum, num) => sum + num, 0);
return <p>Total: {total}p>;
};
reduce
takes an initial sum of 0 and adds each number in the array to it, resulting in the total (35). The component then displays the total.
const people = [
{ id: 1, name: "Alice", city: "NY" },
{ id: 2, name: "Bob", city: "SF" }
];
const peopleById = people.reduce((acc, person) => {
acc[person.id] = person;
return acc;
}, {} as Record<number, {id: number; name: string; city: string}>);
id
. In a React context, you might do this kind of transformation outside the JSX, then use the resulting object for quick access to people by ID.
reduce
when you need to compute a single result (number, object, etc.) from a list – for example, summing scores, merging arrays, or preparing data for rendering.
13. Ternary Operator for Conditional Rendering
condition ? expr1 : expr2
) is a concise way to choose one of two values based on a boolean condition. In JSX, you can use it to conditionally render one element or another inline.
interface HeaderProps { user: string | null; }
const Header: React.FC<HeaderProps> = ({ user }) => {
return (
<header>
{user ? <p>Welcome, {user}!p> : <button>Loginbutton>}
header>
);
};
{ user ?
. If user
is truthy (a non-null username), it shows the welcome message. Otherwise, it shows a "Login" button. This one-liner inside JSX is very readable for simple either/or conditions.
:
produce JSX (or one could be null
to render nothing). This keeps your render logic declarative and succinct.
14. Short-Circuit Logical && for Conditional Rendering
&&
) operator is a common pattern in React to conditionally render something or nothing. In JavaScript, expr1 && expr2
returns expr2
if expr1
is truthy, otherwise it returns expr1
(which would be falsy). We leverage that in JSX: condition &&
will either render
or nothing (if condition is false).
interface LoaderProps { loading: boolean; }
const Loader: React.FC<LoaderProps> = ({ loading }) => {
return (
<div>
{loading && <span className="spinner">Loading...span>}
div>
);
};
loading
is true
, the with "Loading..." will be rendered. If
loading
is false
, the expression {loading && ...}
will short-circuit and result in false
, which React treats as nothing to render (it won’t render a boolean). So the disappears entirely.
isOpen &&
) or sections of a page that should only show under certain conditions.
0
or ""
, be careful because those are falsy and could unintentionally short-circuit. For purely boolean flags or truthy checks, &&
is perfect. (For values where 0 is meaningful, you might use a ternary or check explicitly for != null
.)
15. Optional Chaining (
?.
) for Safe Property Access
?.
) is a JavaScript operator that lets you safely access nested object properties even if an intermediate value might be null or undefined. Instead of throwing an error when encountering a null, it short-circuits and returns undefined for the whole expression.
undefined
. In JSX, this prevents runtime errors and can simplify conditional rendering of deeply nested values.
interface Profile {
name: string;
location?: { city: string; country: string };
}
const UserInfo: React.FC<{ profile: Profile | null }> = ({ profile }) => {
return (
<div>
<p>Name: {profile?.name}p>
<p>City: {profile?.location?.city ?? "Unknown"}p>
div>
);
};
,
profile?.name
will safely yield undefined
if profile
is null (so nothing is rendered after "Name:"). In the second ,
profile?.location?.city
drills into location and then city only if each part is defined. We also used ?? "Unknown"
(nullish coalescing, see next section) to display "Unknown" if city isn’t available.
profile && profile.location && profile.location.city
. The ?.
operator streamlines this. It’s extremely useful for avoiding errors like "Cannot read property 'city' of undefined" in your React app when data might not be fully present yet (e.g., before an API call resolves).
16. Nullish Coalescing (
??
) for Default Values
??
) is an operator that returns the right-hand operand when the left-hand operand is null
or undefined
(nullish), otherwise it returns the left-hand operand. It's like a safer default value operator than ||
because it won't mistakenly default on falsy non-nullish values like 0 or "".
??
lets you do that without treating valid falsy values as missing. This is particularly useful in cases where 0
is a valid value that you don't want to override.
interface HelloProps { username?: string | null; }
const HelloUser: React.FC<HelloProps> = ({ username }) => {
return <p>Hello, {username ?? "Guest"}!p>;
};
username
is null
or undefined
, the expression username ?? "Guest"
evaluates to "Guest"
. If username
is any other value (including an empty string or 0, if that were possible here), it would use that value. By contrast, if we had used username || "Guest"
, an empty string ""
would incorrectly trigger the default to "Guest". With ??
, only nullish values trigger the fallback.
value = props.value ?? 0;
ensures value
is a number – if props.value
is null/undefined, you get 0, but if props.value
were 0 explicitly, you keep 0 (instead of defaulting).
??
when you want to provide a default for missing data but want to allow falsy legitimate values. It makes your component output more predictable for edge cases.
17. Promises for Asynchronous Tasks
.then()
and handle results or errors is key to fetching data and updating state accordingly.
fetch
(which returns a promise) in a React component:
interface User { name: string; }
const UserLoader: React.FC = () => {
const [user, setUser] = React.useState<User | null>(null);
React.useEffect(() => {
// simulate data fetch
fetch('/api/user/123')
.then(response => response.json())
.then(data => {
setUser(data);
})
.catch(error => {
console.error("Failed to load user", error);
});
}, []); // empty dependency array -> run once on mount
if (!user) return <p>Loading...p>;
return <p>Hello, {user.name}p>;
};
fetch
, which returns a promise for a Response. We use .then
to wait for the response and parse JSON, then another .then
to get the data and update state with setUser(data)
. We also attach a .catch
to handle any errors.
useEffect
so it runs when the component mounts. Until the promise resolves, user
is null
and we show a loading message. Once the promise fulfills and we call setUser
, React re-renders and displays the user name.
.then/.catch
as well, since you may see both styles in codebases.
18. Async/Await for Cleaner Async Code
async
, you can use the await
keyword inside it to pause execution until a promise resolves, instead of using .then
chains.
useEffect
or event handlers for clarity.
const UserLoaderAsync: React.FC = () => {
const [user, setUser] = React.useState<User | null>(null);
React.useEffect(() => {
const loadUser = async () => {
try {
const response = await fetch('/api/user/123');
const data = await response.json();
setUser(data);
} catch (error) {
console.error("Failed to load user", error);
}
};
loadUser();
}, []);
if (!user) return <p>Loading...p>;
return <p>Hello, {user.name}p>;
};
loadUser
and call it, because useEffect
itself cannot directly take an async function (it would return a promise which React would treat as a cleanup function). Inside loadUser
, we await
the fetch and then await
the .json()
parsing. This linear style is often easier to follow than nested .then
calls. We wrap it in a try/catch to handle errors (equivalent to .catch).
await
must be marked async
, and that async functions return a promise. In event handlers (like an onClick
), you can directly mark the handler as async
and use await inside it, which is very convenient for sequences of actions (for example, submitting a form then showing a success message after a save completes).
19. Closures in React (Stale State and Variables)
useEffect
or setTimeout, or rely on state values inside callbacks, you may unintentionally use an outdated value of a variable due to how closures work. Understanding closures helps you avoid bugs like a stale state value in an asynchronous callback.
const DelayedAlert: React.FC = () => {
const [count, setCount] = React.useState(0);
const showAlert = () => {
setTimeout(() => {
alert("Count is " + count);
}, 3000);
};
return (
<>
<p>{count}p>
<button onClick={() => setCount(count + 1)}>Incrementbutton>
<button onClick={showAlert}>Show Alert in 3sbutton>
>
);
};
setTimeout
closed over the value of count
at the time showAlert
was called. Even if count
changes later, that inner function doesn't know – it has its own preserved copy from that render.
setTimeout(() => {
alert("Count is " + document.getElementById('countVal')?.textContent);
}, 3000);
useEffect
dependencies and useCallback
exist to manage when closures should refresh. Being aware of closures helps you use these tools correctly.
20. Timers (setTimeout) and useEffect Cleanup
setTimeout
or setInterval
in React requires care to avoid memory leaks or unwanted behavior. If a component sets a timer, that timer might still fire even after the component is unmounted, unless you clear it. React’s useEffect
cleanup function is the place to clear timers.
const AutoHideMessage: React.FC<{ duration: number }> = ({ duration }) => {
const [visible, setVisible] = React.useState(true);
React.useEffect(() => {
const timerId = setTimeout(() => setVisible(false), duration);
return () => {
clearTimeout(timerId); // cleanup if component unmounts before timeout
};
}, [duration]);
return <>{visible && <div>This message will disappear after {duration}msdiv>}>;
};
duration
changes, we set a timeout to update state after duration
milliseconds. The effect returns a cleanup function that clears the timeout. If the component unmounts early, the timeout is cleared and setVisible
won't be called on an unmounted component.
setVisible(false)
. In development, React might warn "Can't perform a React state update on an unmounted component". Clearing timers prevents that.
setInterval
(clear it with clearInterval
) or other subscriptions (like WebSocket events or event listeners – always clean up in useEffect
). By handling cleanup, your component remains well-behaved and doesn't introduce memory leaks or console warnings.
21. Immutability: Avoiding Direct State Mutation
this.state.foo.push(...)
or modifying an object property in state) can lead to React not updating the UI, since setState
/useState
was never called or React thinks nothing changed. Also, state mutations can make debugging harder. Keeping state updates immutable ensures predictable UI updates and enables potential performance optimizations.
// Incorrect: mutating state directly
this.state.items.push(newItem);
this.setState({ items: this.state.items }); // This mutation can cause issues
// Correct: create a new array
this.setState(prev => ({ items: [...prev.items, newItem] }));
this.state.items
. While we do call setState after, we passed the same array object (now modified) to setState. React might still re-render in this case, but it's not a good practice because if you ever skip calling setState, the mutation would be lost or cause inconsistencies. The correct example uses a new array via spread (...prev.items
) so we're sure we have a fresh object.
const [user, setUser] = React.useState({ name: "Alice", points: 0 });
// Incorrect: directly mutate the object
user.points = 100;
setUser(user); // React may not re-render because object reference is same
// Correct: copy the object
setUser(prev => ({ ...prev, points: 100 }));
prev
with {...prev}
and then changing points
, we create a new object. React sees the state value has changed (new reference) and triggers a re-render.
22. Class vs Functional Components (Differences)
render()
method, lifecycle methods like componentDidMount
, and this.state
/this.setState
). Functional components are simpler JavaScript functions that use hooks (like useState
, useEffect
) for state and lifecycle. Knowing both helps in reading older code and writing new code effectively.
// Class Component
interface CounterProps { start?: number; }
class CounterClass extends React.Component<CounterProps, { count: number }> {
state = { count: this.props.start ?? 0 };
componentDidMount() {
console.log("CounterClass mounted");
}
increment = () => this.setState(prev => ({ count: prev.count + 1 }));
render() {
return <button onClick={this.increment}>{this.state.count}button>;
}
}
// Functional Component
const CounterFunc: React.FC<CounterProps> = ({ start = 0 }) => {
const [count, setCount] = React.useState(start);
React.useEffect(() => {
console.log("CounterFunc mounted");
}, []); // run once on mount
return <button onClick={() => setCount(c => c + 1)}>{count}button>;
};
this.state
and this.setState
, plus the lifecycle method componentDidMount
. The functional uses useState
and useEffect
. The functional version is more concise and avoids this
altogether.
state
property and this.setState
(which merges state partially), functional uses useState
(you replace state completely or handle merging manually for objects).componentDidMount
, componentDidUpdate
, componentWillUnmount
. Functional uses useEffect
for all these (the effect's dependencies determine when it runs).this
: Class components require dealing with this
(and binding if passing methods around). Functional components have no this
— they close over variables directly.
23. Typing Component Props with TypeScript
// Define an interface for props
interface GreetingProps { name: string; age?: number; }
// Functional Component with typed props
const Greeting: React.FC<GreetingProps> = ({ name, age }) => (
<div>
<p>Hello, {name}!p>
{age !== undefined && <p>You are {age} years old.p>}
div>
);
// Class Component with typed props
class GreetingClass extends React.Component<GreetingProps> {
render() {
const { name, age } = this.props;
return (
<div>
<p>Hello, {name}!p>
{age !== undefined && <p>You are {age} years old.p>}
div>
);
}
}
GreetingProps
requiring a name: string
and an optional age: number
. In the functional component, we annotate React.FC
(FC stands for Function Component) which tells TypeScript this component should be called with name
and optionally age
. In the class component, we extend React.Component
to accomplish the same.
without a name, or with a name that's not a string, TypeScript will error. Similarly, it will autocomplete prop names and types for you.
type GreetingProps = { ... }
), which works similarly. The key part is attaching that type to the component (either via React.FC
or as a generic to React.Component
for classes).
24. Typing State in Class Components (and useState)
React.Component
. For functional components, you don't explicitly type "state", but you type the value passed to useState
or let TypeScript infer it from the initial value.
this.setState
or use state values, you respect the shape of the state. It prevents you from accidentally storing the wrong data type in state or accessing a non-existent state property.
interface CounterState { count: number; }
class Counter extends React.Component<{}, CounterState> {
state: CounterState = { count: 0 };
increment = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return <button onClick={this.increment}>{this.state.count}button>;
}
}
CounterState
and told the class that its state conforms to that. We initialized state
accordingly. Now this.state.count
is known to be a number, and this.setState
will only allow updates that match the state shape (e.g., if we tried this.setState({ count: "hello" })
, TypeScript would error out because "hello"
is not a number).
const [count, setCount] = React.useState<number>(0);
setCount(prev => prev + 1);
to useState
to indicate the state type. This is often optional because TS can infer from the initial value 0
that it's a number. But if your initial state is ambiguous (like null
or an empty array), you might supply the type.
// state is an array of strings, initial empty
const [items, setItems] = React.useState<string[]>([]);
useState
generic or initial value. Typing class state is done via the class generics. Both approaches ensure you treat state as the right type throughout the component.
25. Generics in Components and Hooks
T
and a render function for T
, and the compiler will enforce consistency.
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>): JSX.Element {
return <ul>{items.map((item, idx) => <li key={idx}>{renderItem(item)}li>)}ul>;
}
// Usage:
<List
items={['Alice', 'Bob', 'Charlie']}
renderItem={(name) => <span>{name.toUpperCase()}span>}
/>
List
component above is generic (function List
). It takes items
of type T[]
and a renderItem
function that knows how to render an item: T
. When we use
with an array of strings, TypeScript infers T
as string
. Inside the renderItem
, name
is correctly typed as string, so toUpperCase()
is allowed. If we tried to use renderItem={(name) => name * 2}
TS would error, because name
is a string, not a number.
function useFilter<T>(items: T[], predicate: (item: T) => boolean): T[] {
return React.useMemo(() => items.filter(predicate), [items, predicate]);
}
// Usage:
const evenNumbers = useFilter([1,2,3,4], num => num % 2 === 0);
useFilter
works for any type T
, and TS ensures the predicate receives that same T
. In usage, T
becomes number because we passed an array of numbers.
26. Union and Literal Types for Flexible Props
type
is X, then these other props must be present". This leads to more robust components and fewer runtime errors.
type AlertProps =
| { type: 'success'; message: string }
| { type: 'error'; error: Error };
const Alert: React.FC<AlertProps> = (props) => {
if (props.type === 'success') {
return <div className="alert-success">✅ {props.message}div>;
} else {
return <div className="alert-error">❌ {props.error.message}div>;
}
};
// Usage:
<Alert type="success" message="Operation completed." />
<Alert type="error" error={new Error("Something went wrong")} />
AlertProps
is a union of two object types. If type
is 'success'
, then a message: string
is expected. If type
is 'error'
, an error: Error
is expected. Inside the component, we discriminate with if (props.type === 'success')
. TypeScript then knows within that block that props
is the first variant (with message
available), and in the else it knows props
is the error variant (with an error
object). This is called type narrowing.
Alert
tries to pass inconsistent props (like type="success"
but provide an error
prop, or missing message
), TypeScript will error. This makes the API of Alert
very clear and strict.
type Size = 'small' | 'medium' | 'large';
interface ButtonProps { size: Size; label: string; }
size
can only be "small", "medium", or "large". If someone passes , TS will catch it.
27. Typed Event Handlers in React
React.ChangeEvent
, a click event on a button is React.MouseEvent
, and so on. Using these types for event handler parameters ensures you access the right properties without errors.
target
and its value) and you ensure you're handling the correct event type (for instance, differentiating an onChange
of an vs an
onSubmit
of a ). It also communicates to other developers what kind of event to expect.
const NameInput: React.FC = () => {
const [name, setName] = React.useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
return <input value={name} onChange={handleChange} />;
};
handleChange
is explicitly typed as a function that takes a React.ChangeEvent
. That tells us e.target
is an HTML input element, so it has a value
property (which is a string for text inputs). Inside, we do e.target.value
confidently. If we mistyped something (say e.tARGET.value
or accessed a nonexistent property), TS would error.
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
// ...
};
<button onClick={handleClick}>Click mebutton>
React.FormEvent
.
React.*Event
types from @types/react
for the specific element you are dealing with. Common ones include ChangeEvent
, MouseEvent
, KeyboardEvent
, FormEvent
, etc., each parameterized by the HTML element type.
onChange
handler with the appropriate ChangeEvent
type for an input.
28. Using Refs in React with TypeScript
is not mistakenly used on a
or something. It also helps TS know that
ref.current
might be HTMLInputElement | null
, for instance.
useRef
for a DOM element:
const FocusableInput: React.FC = () => {
const inputRef = React.useRef<HTMLInputElement>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus the inputbutton>
>
);
};
useRef
meaning this ref will eventually point to an HTMLInputElement (or be null initially). Later, we can call inputRef.current?.focus()
. TypeScript knows current
is an HTMLInputElement | null
, so it permits .focus()
(which exists on HTMLInputElement
) and we use ?.
in case it's null. If we tried to call a method that isn't on HTMLInputElement, TS would error.
React.createRef
similarly. Example:
class Scroller extends React.Component {
private listRef = React.createRef<HTMLUListElement>();
scrollToBottom = () => {
const ul = this.listRef.current;
ul?.lastElementChild?.scrollIntoView();
};
render() {
return (
<div>
<ul ref={this.listRef}>{/* ...list items... */}ul>
<button onClick={this.scrollToBottom}>Scroll to bottombutton>
div>
);
}
}
React.RefObject
), or when using useRef
to hold a mutable value (then you type it as that value type).
29. Functional State Updates with Previous State
setState
(in classes, pass a function to this.setState
; in hooks, pass a function to setState
). This ensures you always get the latest state value, especially when state updates might be batched or an update is triggered in quick succession.
state
variables directly when setting new state, you might close over a stale value (similar to the closure topic earlier). The functional update form provides the current state as an argument, guaranteeing you use the most up-to-date value. It's crucial in cases where you do multiple updates in one go or in event handlers that might run before state is updated.
const DoubleIncrement: React.FC = () => {
const [count, setCount] = React.useState(0);
const incrementTwice = () => {
// WRONG: this would only increment once because `count` is stale for second call
// setCount(count + 1);
// setCount(count + 1);
// RIGHT: use functional updates to ensure each uses latest result
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>{count}p>
<button onClick={incrementTwice}>Increment twicebutton>
div>
);
};
setCount(count + 1); setCount(count + 1);
, the second call would still see the old count
(because state doesn't update until after the function execution). The result would be count
increased by 1 instead of 2. By using prev => prev + 1
each time, the first call sets it to old+1, the second call uses that updated value (prev) and adds 1 again. The end result is count
+2 as expected.
this.setState(prevState => ({ volume: prevState.volume + 1 }));
prevState
.
setFlag(prev => !prev)
), pushing to arrays (setList(prev => [...prev, newItem])
), etc. This ensures correctness even if multiple updates happen or other state updates intervene.
30. ES Module Imports/Exports in React Projects
// File: components/MyButton.tsx
import React from 'react';
interface MyButtonProps { label: string; }
const MyButton: React.FC<MyButtonProps> = ({ label }) => (
<button>{label}button>
);
export default MyButton;
MyButton
is exported as the default from its file. This means when we import it elsewhere, we can choose any name:
// File: App.tsx
import MyButton from './components/MyButton'; // default import, name matches exported component for clarity
// ...
<MyButton label="Click me" />
// File: utils/math.ts
export function add(a: number, b: number) { return a + b; }
export const PI = 3.14;
import { add, PI } from './utils/math';
console.log(add(2,2), PI);
{ }
and the names must match (unless we do aliasing like import { add as sum }
). With default exports, no curly braces and we choose the name (though typically use the original name for sanity).