Code Evolution React Hook
Code Evolution React Hook
Here are the detailed notes with improvements, added code snippets to
demonstrate the problems and how they were solved using React Hooks:
🛑 Key Point:
Hooks don’t work inside classes, and they allow you to use React features
like state, context, and others in functional components.
⚙️ Example:
Before Hooks: Only class components could hold state.
🔹 Why Hooks?
❓ Motivation:
Why did React team introduce Hooks? Here's the why behind it:
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</butt
on>
</div>
);
}
}
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increm
ent</button>
</div>
);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
componentDidMount() {
fetchData().then((data) => {
this.setState({ data, loading: false });
});
}
componentWillUnmount() {
cleanupEventListener();
}
render() {
const { data, loading } = this.state;
return loading ? <p>Loading...</p> : <p>{JSON.string
ify(data)}</p>;
}
}
useEffect(() => {
fetchData().then((data) => {
setData(data);
setLoading(false);
They are designed to coexist with existing React features like class
components.
🔄 Backward Compatibility:
Hooks don’t introduce breaking changes.
You can gradually adopt hooks without completely rewriting your app.
🎯 Hooks’ Purpose:
Hooks don’t replace your existing knowledge of React concepts like
props, state, context, refs, or lifecycle methods.
Instead, they provide a more direct and intuitive API for working with
these concepts in functional components.
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment
</button>
</div>
);
}
🔹 Summary
📌 Key Takeaways:
1. Hooks: A new feature in React 16.8 that allows you to use React
features (like state) without classes.
2. Benefits:
3. Opt-In: You can continue using classes if preferred. Hooks are optional
and backward compatible.
🛠️ Step-by-Step Guide:
1️⃣ Class Component Implementation
Steps to build a counter with class components:
Code Example:
incrementCount = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<button onClick={this.incrementCount}>
Count: {this.state.count}
</button>
);
}
}
Usage:
Include
<ClassCounter /> in your App.js and test in the browser.
Code Example:
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
};
Explanation:
2. Why useState ?
3. Array Destructuring:
Example:
Destructuring assigns:
📜 Rules of Hooks:
1. Call Hooks at the Top Level:
📝 Recap:
Class components are no longer necessary for state management with the
advent of hooks.
🛠️ Step-by-Step Guide:
1️⃣ Initial Setup:
Create the Component:
File:
HookCounterThree.js
return (
<div>
<form>
<input
type="text"
value={name.firstName}
onChange={(e) =>
setName({ ...name, firstName: e.
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="First Name"
value={name.firstName}
onChangeText={(value) =>
The spread syntax ( ... ) in JavaScript allows iterable elements like arrays or
strings to be expanded in places where zero or more arguments or elements
are expected.
Function Arguments: When used in function calls, the spread syntax allows
elements of an array to be passed as individual arguments. For instance,
Math.max(...[1, 2, 3]) is equivalent to Math.max(1, 2, 3) .
It's important to note that the spread syntax performs a shallow copy of the
elements or properties. This means that if the original object contains
references to other objects, only the references are copied, not the actual
objects. Therefore, changes to nested objects will affect both the original and
the copied object.
Kinsta®
In summary, the spread syntax provides a concise and readable way to expand
elements, pass arguments, or merge objects by leveraging the iterable protocol
and object property enumeration in JavaScript.
2. Two-Way Binding:
<input
type="text"
value={name.firstName}
onChange={(e) => setName({ firstName: e.target.value
})}
/>
When the first name is updated, the lastName field disappears from the
state. Why?
Reason: useState does not merge the existing state. It replaces it with
the new object.
Explanation:
return (
<div>
<form>
<input
type="text"
value={name.firstName}
onChange={(e) =>
setName({ ...name, firstName: e.tar
get.value })
}
/>
<input
type="text"
value={name.lastName}
onChange={(e) =>
setName({ ...name, lastName: e.targ
et.value })
}
/>
<h2>Your first name is: {name.firstName}</h
2>
<h2>Your last name is: {name.lastName}</h2>
<p>{JSON.stringify(name)}</p>
</form>
</div>
🔑 Key Points:
1. useState Behavior:
2. Solution:
Use the spread operator to manually merge the existing state with the
updated properties:
📝 Recap:
Use objects in state for complex state management.
Always merge object updates manually with the spread operator when
using useState .
Stay tuned for the next example, where we'll explore working with arrays in
state! 🎉
3. Rendering Items:
items.map((item) => (
<li key={item.id}>{item.value}</li>
));
This triggers the addItem function to add new items to the array.
5. addItem Function:
Define the addItem function to append a new object to the items array:
Key points:
Appending a new object to the copied array avoids directly mutating the
state.
Execution Flow
1. Initially, items is an empty array ( [] ).
Example:
{ id: 0, value: 5 }
{ id: 1, value: 7 }
4. This process continues, appending new objects for every button click.
Summary
The useState hook:
Returns an array:
Example usage:
setItems([...items, newItem]);
🚀 Key Takeaways
Use the spread operator for state immutability.
1. Initial Render:
componentDidMount() {
document.title = `Clicked ${this.state.count} times
`;
}
2. On State Update:
componentDidUpdate() {
document.title = `Clicked ${this.state.count} times
3. Problem:
1. Set Timer:
componentDidMount() {
this.interval = setInterval(() => {
console.log('Hello');
}, 5000);
}
2. Clean Up Timer:
componentWillUnmount() {
clearInterval(this.interval);
}
3. Problem:
Unrelated logic (e.g., DOM updates and timers) is often placed together
in componentDidMount .
componentDidMount
componentDidUpdate
componentWillUnmount
2. Advantages:
Next Steps
In the next example, we will:
Initial render.
Subsequent renders.
🚀 Key Takeaways
useEffect is a powerful hook that simplifies managing side effects in
functional components.
Fetch data.
📹 Introduction:
In the previous video: We explored the useEffect hook and how it allows us
to perform side effects in functional components. It mimics lifecycle
methods like componentDidMount , componentDidUpdate , and componentWillUnmount
Goal for this video: Implement side effects such as updating the document
title using the useEffect hook in functional components.
🔧Components):
Class Component Example (Before Functional
// ComponentDidMount
componentDidMount() {
document.title = `You clicked ${this.state.count} times
`;
}
// ComponentDidUpdate
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times
`;
}
render() {
return (
<button onClick={() => this.setState({ count: this.st
ate.count + 1 })}>
Clicked {this.state.count} times
</button>
);
}
}
Key points:
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
};
Key points:
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
};
The reason it runs after every render: To mimic the behavior of lifecycle
methods like componentDidMount and componentDidUpdate from class components.
useEffect runs inside the component body, so you can directly access
state (e.g., count ) and props without extra code.
💡 Key Takeaways:
useEffect is called after every render.
For side effects that need to run after the first and subsequent renders
(like document updates), useEffect is perfect.
Basic Syntax:
useEffect(() => {
// Your side effect code here
});
You can access state and props directly within the useEffect function
without extra setup.
⏭️ What's Next?
We will dive deeper into customizing when the useEffect hook should run
(e.g., running it only once or when certain state changes).
📹 Introduction:
In the last video: We explored how useEffect is called after every render in
functional components. However, calling useEffect on every render can
cause performance issues.
Goal for this video: Learn how to conditionally run effects to improve
performance by only executing useEffect when certain variables change.
// ComponentDidUpdate
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} tim
es`;
console.log('Updating document title');
}
render() {
return (
<>
<button onClick={this.handleClick}>Clicked {this.st
ate.count} times</button>
<input
type="text"
value={this.state.name}
onChange={this.handleInputChange}
/>
</>
);
}
}
Key Points:
return (
<>
<button onClick={() => setCount(count + 1)}>Clicked
{count} times</button>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</>
);
};
Key Points:
Effect Hook: The useEffect runs after every render, updating the
document title even when typing in the input, which is not optimal.
We want the effect to only run when the count value changes and not when
the name is updated. This can be achieved by passing a dependency array
as the second argument to useEffect .
useEffect(() => {
document.title = `You clicked ${count} times`;
console.log('Updating document title');
}, [count]); // Only run effect when `count` changes
Key Points:
💡 Key Takeaways:
with Dependency Array: To conditionally run an effect, pass a
useEffect
second argument (an array) with the state or props you want to watch. The
effect will only run if one of those values changes.
Example:
useEffect(() => {
// Your side effect code here
}, [someStateOrProp]); // Effect runs only if `someSt
ateOrProp` changes
With Dependency Array: We only run useEffect when the count value
changes, optimizing performance.
⏭️ What's Next?
In the next video, we'll explore more examples and dive deeper into the
behavior of useEffect and the dependency array.
1. State Setup:
constructor() {
this.state = { x: 0, y: 0 };
}
2. componentDidMount Hook:
componentDidMount() {
window.addEventListener('mousemove', this.logMousePosi
tion);
}
3. Rendering:
This ensures that the event listener only sets up once, on initial mount,
mimicking componentDidMount behavior.
render() {
const { x, y } = this.state;
return (
<div>
X: {x}, Y: {y}
</div>
);
}
}
useEffect(() => {
console.log('useEffect called');
window.addEventListener('mousemove', logMousePosition);
return () => {
window.removeEventListener('mousemove', logMousePosit
ion);
};
}, []); // Empty dependency array
return (
<div>
X: {mousePos.x}, Y: {mousePos.y}
</div>
);
};
This setup ensures that the event listener is added once on initial
render and not on subsequent re-renders.
The cleanup function ( return () => {...} ) removes the event listener
when the component is unmounted. This is important to avoid memory
leaks.
return () => {
window.removeEventListener('mousemove', logMousePositio
n);
};
2. useEffect Hook:
🚀 Conclusion:
By using useEffect with an empty dependency array, we mimic the
componentDidMount lifecycle method from class components.
This allows us to run effects once when the component is first mounted,
making it a powerful tool for side effects in functional components.
🏗️ Objective:
Mimic componentWillUnmount in class components using the useEffect hook in
functional components.
componentWillUnmount() {
window.removeEventListener('mousemove', this.logMousePosi
2. Problem:
If you toggle the visibility of this component (unmount it), the event
listener stays active, causing warnings about state updates on an
unmounted component.
🖥️ Step-by-Step Solution:
🖱️ MouseContainer Component:
State Setup: We maintain a display state to toggle the visibility of the
MouseTracker component.
return (
<div>
<button onClick={() => setDisplay(!display)}>
Toggle Display
</button>
{display && <MouseTracker />}
</div>
);
};
useEffect(() => {
const logMousePosition = (e) => {
setMousePos({ x: e.clientX, y: e.clientY });
};
return (
<div>
X: {mousePos.x}, Y: {mousePos.y}
</div>
);
};
2. Cleanup Function:
The effect runs only once, similar to componentDidMount , since the array is
empty. The cleanup will occur when the component is unmounted.
💡 Takeaways:
Cleanup with useEffect : Always return a cleanup function when working
with side effects that require resource management (e.g., event listeners,
subscriptions).
Return Cleanup from useEffect : The cleanup function allows you to manage
state or subscriptions when the component unmounts, similar to
componentWillUnmount in class components.
return (
<div>
X: {mousePos.x}, Y: {mousePos.y}
</div>
);
};
🔑 Conclusion:
Cleanup is essential for managing resources in functional
useEffect
🧑💻 Objective:
Create an interval counter that automatically increments every second.
componentDidMount() {
this.interval = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <div>Count: {this.state.count}</div>;
}
}
2. Interval Setup: Use setInterval to increment the count value every second,
simulating componentDidMount .
useEffect(() => {
const interval = setInterval(tick, 1000);
return () => {
clearInterval(interval); // Cleanup
};
}, []); // Empty dependency array
🧐 Problem:
The counter doesn't increment as expected. It shows the value 1 but
doesn't update every second.
The empty array ( [] ) means the effect runs only once—on the initial
render.
But state updates (like the setCount call inside the tick function) do not
trigger the effect again. This is because we didn't list count as a
dependency.
3. Result:
tick increments count to 1 , but the effect doesn't run again, so the
interval doesn't keep updating the state as expected.
🔧 Solution:
1. Fix: Add count to the dependency array. This ensures that the setInterval
useEffect(() => {
const interval = setInterval(tick, 1000);
return () => {
clearInterval(interval); // Cleanup
1. Alternative Fix: Use the functional update form of setCount . This avoids the
need to include count as a dependency.
useEffect(() => {
const interval = setInterval(tick, 1000);
return () => {
clearInterval(interval); // Cleanup
};
}, []); // No need to add count to the dependency array
Why this works: The functional update form of setCount allows React to
keep track of the previous state ( prevCount ), and we no longer need to
watch for changes in the count state.
💡 Key Takeaways:
1. Dependency Array:
If you need to use a value (like count ) inside useEffect , include it in the
dependency array, or else the effect may not run as expected.
Empty array ( [] ) means the effect runs only once on the initial render.
3. Common Mistake:
useEffect(() => {
const interval = setInterval(tick, 1000);
return () => {
clearInterval(interval); // Cleanup
};
}, [count]); // Dependency array with count
useEffect(() => {
const interval = setInterval(tick, 1000);
🔑 Conclusion:
State management and dependencies in useEffect can be tricky for
beginners, especially when migrating from class components.
Remember: The dependency array is not just about when to rerun the
effect—it's about tracking changes to values used in the effect.
Use the functional update form of setState when you need to avoid state
dependencies in the effect.
🧑💻 Objective:
Fetch data from an API endpoint (using Axios) and render it in the UI.
Understand how to manage state and side effects when working with
external data sources.
Apply useEffect to trigger the data fetching only once when the component
mounts.
🏗️ Step-by-Step Implementation:
Code evolution React hook 47
1. Install Axios:
Axios is used here for making HTTP requests. While the Fetch API is a
native browser API, Axios simplifies error handling and provides a more
readable syntax.
After installation, the axios package will be listed in your package.json file.
2. Component Setup:
Create a new file dataFetching.js in your project folder.
4. State Setup:
Create a state variable posts using the useState hook. Initialize it with an
empty array [] to store the fetched data.
return (
<div>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
⚠️Problem Encountered:
After fetching the data and logging the response to the console, we notice
that the UI doesn’t display the fetched posts.
Issue: The state was not being updated after fetching data.
Cause: This happens because the useEffect was being triggered on every
render. By default, useEffect runs after every render cycle, and since we
were updating the state in setPosts , it caused re-renders that triggered
another API request.
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => {
setPosts(response.data);
})
.catch(error => {
console.log("Error fetching data: ", error);
});
}, []); // Empty dependency array ensures data is fetched o
nly once
🔧 Code Recap:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => {
setPosts(response.data); // Update state with fetch
ed data
})
.catch(error => {
console.log("Error fetching data: ", error);
return (
<div>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
💡 Key Takeaways:
1. State Management:
Always update state with the data once the request is successful to
trigger a re-render.
3. Error Handling:
Axios simplifies error handling and is often preferred over the Fetch API
due to its ability to handle both HTTP errors and network errors more
📑 Next Steps:
In the next video, we will expand on this example and learn how to fetch
individual posts by entering a post ID and making the request with a button
click.
🧑💻 Objective:
Fetch individual posts by appending the post ID to the URL endpoint.
Handle input from the user to dynamically change the post ID and trigger a
new API request.
Improve the solution by triggering the API request only on a button click,
instead of on every keystroke.
🏗️ Step-by-Step Implementation:
1. Fetching an Individual Post by ID:
To fetch a single post, the API endpoint will be:
/posts/{id}
useEffect(() => {
axios.get(`https://jsonplaceholder.typicode.com/posts/${i
d}`)
.then(response => {
setPost(response.data); // Set the fetched post data
})
.catch(error => {
console.log("Error fetching post: ", error);
});
}, [id]); // Dependency array now includes `id` so effect r
uns when ID changes
return (
<div>
<input
type="text"
5. Testing:
Initially, the post with ID 1 will be displayed.
When the user changes the ID (e.g., 2 or 10 ), the new post will be fetched
and displayed.
Cause: The useEffect hook is triggered each time the id changes, which
leads to re-fetching the data on each keystroke.
Solution: Move the request to a button click handler so the effect is only
triggered when the user is ready (e.g., after they press a button).
Here’s how you can modify the code to trigger the request only when a button
is clicked:
Create a button element and handle its onClick event to trigger the API
request.
return (
<div>
<input
type="text"
value={id}
onChange={(e) => setId(e.target.value)} // Update ID
on change
/>
<button onClick={handleFetchPost}>Fetch Post</button>
{/* Trigger on button click */}
<h3>{post.title}</h3>
</div>
);
1. Explanation:
💡 Key Takeaways:
1. Dynamic Fetching by ID:
2. Controlled Input:
return (
<div>
<input
type="text"
value={id}
onChange={(e) => setId(e.target.value)} // Update s
tate on change
/>
<button onClick={handleFetchPost}>Fetch Post</button>
{/* Trigger fetch on button click */}
🔧 Next Steps:
In the next video, we will discuss how to improve the UI/UX, possibly with
loading states or error handling for a more polished experience.
🧑💻 Objective:
Trigger an API request based on a button click instead of using the
onChange event for every keystroke in the input field.
Use the useEffect hook in a way that it only re-fetches data when the button
is clicked, not on each change of the input value.
🏗️ Step-by-Step Implementation:
1. Create State Variables:
We will create two state variables:
2. Another for storing the post ID that will trigger the useEffect hook
( idFromButtonClick ).
return (
<div>
<input
type="text"
value={id} // Controlled input for post ID
onChange={(e) => setId(e.target.value)} // Update id
on input change
/>
<button onClick={handleClick}>Fetch Post</button> {/* B
utton to trigger fetch */}
</div>
);
This ensures the data is fetched only when the button is clicked.
return (
<div>
<input
type="text"
value={id}
onChange={(e) => setId(e.target.value)} // Controlled
input
/>
<button onClick={handleClick}>Fetch Post</button> {/* F
etch on button click */}
<h3>{post.title}</h3> {/* Display the fetched post's ti
tle */}
</div>
);
⚠️Key Points:
1. State Management:
id state is used to track the user input for the post ID.
dependency array.
This approach ensures the API is fetched only after the button is
clicked, instead of fetching on every input change.
3. Effect Hook:
🧪 Testing:
Initially, the first post is shown (since the default ID is 1 ).
Changing the input to a new value (e.g., 2 , 3 , etc.) does not trigger a
request until the user clicks the "Fetch Post" button.
When the button is clicked, the post for the entered ID is fetched and
displayed.
return (
<div>
<input
type="text"
value={id}
onChange={(e) => setId(e.target.value)} // Controll
ed input
/>
<button onClick={handleClick}>Fetch Post</button> {/*
Button triggers data fetch */}
<h3>{post.title}</h3> {/* Render the title of the fet
ched post */}
</div>
);
};
💡 Key Takeaways:
1. Button Click Triggers Effect: By using a button click to set the state that the
useEffect depends on, you control when the data is fetched.
2. Controlled Input: The input field is controlled, with the id state being
updated based on the user's input.
Component A
Component B
Component D (nested in B)
Component C
Component E
Component F (nested in E)
Requirement:
You need to display the logged-in username in Components A, D, and F.
F .
If your component tree is deeply nested (e.g., 5–10 levels deep), this prop
drilling becomes tedious and problematic, especially when the data (like a
The more levels you add, the more you need to pass props manually,
leading to "prop drilling".
Instead of passing props manually at every level, Context lets you share
data across the component tree without the need to manually forward
props through every layer.
How it Works:
1. Create a Context:
2. Provide Context:
3. Consume Context:
The components that need access to the shared data use the Consumer
Cleaner Code: Reduces the need for deeply nested components that
simply pass data down.
🛠️ What’s Next?:
In the next video, we will explore how Context API was used before the
introduction of hooks. This will help us understand the improvements and
benefits brought by the useContext hook.
You're right to point that out! In the code for consuming multiple contexts, I
missed the return statement in the nested render props pattern. Let me correct
that and show you the correct version of the code.
// App Component
const App = () => {
return (
<userContext.Provider value="Vishwas">
<channelContext.Provider value="Code Evolution">
<ComponentC />
</channelContext.Provider>
</userContext.Provider>
);
};
// Component C
const ComponentC = () => {
return <ComponentE />;
};
// Component E
const ComponentE = () => {
return <ComponentF />;
};
What's Fixed:
Return Statements: The JSX inside the consumers must be returned for the
proper rendering to happen. In the previous example, I missed the actual
return keyword inside the consumers.
Expected Output:
In Component F, we should now correctly display the message: Vishwas is on
Code Evolution .
This updated code should work properly! Thanks for pointing it out.
React. Let’s break it down with clear steps, code snippets, and explanations.
2. Providing a context value high in the component tree using the <Provider>
component.
property.
📚 Step-by-Step Implementation
1️⃣ Create a Context
import React, { createContext } from 'react';
// Create contexts
export const UserContext = createContext();
export const ChannelContext = createContext();
function App() {
return (
<UserContext.Provider value="John Doe">
<ChannelContext.Provider value="React Tutorials">
<ComponentE />
</ChannelContext.Provider>
</UserContext.Provider>
);
}
function ComponentE() {
// Access context values
const user = useContext(UserContext);
const channel = useContext(ChannelContext);
return (
👤 User: {user}</h1>
<div>
</div>
);
}
💬 Closing Thoughts
useContext is a simple yet powerful hook.
It’s ideal for most scenarios where state sharing across components is
necessary.
🌟 Overview of useReducer
useReducer is a React Hook designed for managing state transitions.
// Reducer function
const reducer = (accumulator, currentValue) => accumulator
+ currentValue;
console.log(total); // Output: 10
🔑 Key Points:
reduce Parameters:
🌟 Connection to useReducer
The useReducer hook uses a similar concept but applies it to React state
management.
reduce has accumulator and current value useReducer has state and action
📚 Syntax of useReducer
Parameters:
1. Reducer Function: (state, action) => newState
Processes the current state and an action to return the next state.
Return Value:
state : The current state value.
function Counter() {
// Initialize the reducer with the reducer function and i
nitial state
const [state, dispatch] = useReducer(reducer, { count: 0
});
return (
<div>
<h1>Counter: {state.count}</h1>
➕ Increment</button>
<button onClick={() => dispatch({ type: 'increment'
})}>
})}>➖ Decrement</button>
<button onClick={() => dispatch({ type: 'decrement'
>🔄 Reset</button>
<button onClick={() => dispatch({ type: 'reset' })}
</div>
);
}
🖥️ Output in Browser:
➕ ➖ Decrement] [🔄 Reset]
Counter: 0
[ Increment] [
💡 Key Concepts:
Actions: Represent what should happen (e.g., 'increment' , 'decrement' ).
Reducer: Handles state transitions based on the current state and the
action.
Simple state (e.g., a single counter). Complex state with multiple sub-values.
State updates are independent. State transitions depend on the previous state.
Minimal logic for state updates. Complex logic for state updates.
🌟 Summary
useReducer is a hook for state management in React.
Introduction
const initialState = 0;
method.
Final Code
const initialState = 0;
return (
🧠 How It Works
1. useReducer Hook:
2. Reducer Logic:
3. Button Clicks:
Introduction
In this example, we enhance the useReducer usage by converting:
This approach mirrors the pattern often seen in Redux and provides flexibility
for:
2. Copy the code from the previous example ( CounterOne ) and update the
component name to CounterTwo .
const initialState = {
firstCounter: 0,
};
const initialState = {
firstCounter: 0,
Final Code
const initialState = {
firstCounter: 0,
secondCounter: 10,
};
return (
<div>
<h1>Counters</h1>
<div>First Counter: {count.firstCounter}</div>
<button onClick={() => dispatch({ type: "increm
ent", value: 1 })}>Increment</button>
Key Takeaways
1. State as Object:
2. Action as Object:
Allows passing additional data (e.g., value ) to the reducer for complex
updates.
3. Scalable Design:
4. Flexibility:
Easily add more counters or actions by extending the state and reducer .
Introduction
In this example, we demonstrate how to manage multiple state variables with
identical transitions using multiple useReducer hooks. This approach simplifies
the code by avoiding:
const initialState = 0;
return (
<div>
<h1>Counter 1</h1>
<div>Count: {count}</div>
<button onClick={() => dispatch("increment")}>I
ncrement</button>
<button onClick={() => dispatch("decrement")}>D
ecrement</button>
<button onClick={() => dispatch("reset")}>Reset
</button>
</div>
);
};
Update the variables for the second counter ( countTwo and dispatchTwo ).
<div>
<h1>Counter 2</h1>
<div>Count: {countTwo}</div>
<button onClick={() => dispatchTwo("increment")}>Increm
Final Code
const initialState = 0;
return (
<div>
<h1>Counter 1</h1>
<div>Count: {countOne}</div>
<button onClick={() => dispatchOne("incremen
<h1>Counter 2</h1>
<div>Count: {countTwo}</div>
<button onClick={() => dispatchTwo("incremen
t")}>Increment</button>
<button onClick={() => dispatchTwo("decremen
t")}>Decrement</button>
<button onClick={() => dispatchTwo("reset")}>Re
set</button>
</div>
);
};
Key Takeaways
1. Simplicity:
When multiple state variables have the same transitions, using multiple
useReducer hooks avoids the complexity of combining state into a single
object.
3. Reusability:
4. Code Readability:
Introduction
Managing global state in React can become challenging with deeply nested
components. Instead of prop drilling, we combine the power of useReducer and
useContext to share and manage global state efficiently.
This example demonstrates:
2. Sharing the counter's state and dispatch method across deeply nested
components using useContext .
const initialState = 0;
return (
<CountContext.Provider value={{ countState: coun
t, countDispatch: dispatch }}>
<div>
<h1>Global Counter: {count}</h1>
<ComponentA />
<ComponentB />
<ComponentC />
</div>
</CountContext.Provider>
);
};
export default App;
Key Points:
Provide both count (state) and dispatch methods as the value prop.
return (
<div>
<h2>Component A Count: {countState}</h2>
<button onClick={() => countDispatch("increm
ent")}>Increment</button>
<button onClick={() => countDispatch("decrem
ent")}>Decrement</button>
<button onClick={() => countDispatch("rese
t")}>Reset</button>
</div>
);
};
src/
├── App.js
├── components/
const initialState = 0;
return (
<CountContext.Provider value={{ countState: count,
countDispatch: dispatch }}>
Example: ComponentA.js
return (
<div>
<h2>Component A Count: {countState}</h2>
<button onClick={() => countDispatch("incremen
t")}>Increment</button>
<button onClick={() => countDispatch("decremen
t")}>Decrement</button>
<button onClick={() => countDispatch("reset")}>
Reset</button>
</div>
);
};
The useContext API eliminates the need for passing props down multiple
levels, especially in deeply nested component trees.
3. Reusability:
4. Scalability:
This pattern is scalable for managing complex global states and can be
extended to multiple contexts if needed.
🎯 Goals:
Fetch data from an API when the component mounts.
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/posts/
1')
.then((response) => {
setLoading(false); // Data fetched
setPost(response.data); // Store the fetched po
return (
<div>
{loading ? (
'Loading...'
) : error ? (
<p>{error}</p>
) : (
<h1>{post.title}</h1>
)}
</div>
);
💡 Complete Code
import React, { useState, useEffect } from 'react';
import axios from 'axios';
useEffect(() => {
axios.get('https://jsonplaceholder.typicode.com/pos
ts/1')
.then((response) => {
setLoading(false);
setPost(response.data);
setError('');
})
.catch(() => {
setLoading(false);
setPost({});
setError('Something went wrong');
});
}, []);
return (
<div>
{loading ? (
'Loading...'
) : error ? (
<p>{error}</p>
) : (
<h1>{post.title}</h1>
)}
</div>
);
};
📌 Key Observations
useState is used to manage multiple states ( loading , error , post ).
State transitions are handled in the then and catch blocks of the Axios
request.
🔜 Next Step
In the following example, we will achieve the same functionality using the
useReducer hook to better manage complex state transitions. 🎥
Example: Managing several state variables like loading , error , post , etc.
Use useReducer when the state variables are closely related and dependent
on a specific action.
Example: A loading state, error state, and data state are tied together in
a data fetching operation. They should be managed together in the
same reducer for consistency and maintainability.
Best for simple, local state management, especially for primitive types or
when only a few variables are updated at a time.
useReducer :
Best for managing complex state logic, multiple interdependent state
variables, or global state in larger apps.
🔚 Conclusion:
In general, if you're working with simple state or managing local state, useState
is the go-to. For complex, interrelated states, or when dealing with global state,
useReducer shines.
💡
Transcript Notes for Performance Optimization with
useCallback Hook
function ParentComponent() {
const [age, setAge] = useState(25);
const [salary, setSalary] = useState(50000);
return (
<div>
<Title />
<Count label="Age" value={age} />
<Button onClick={incrementAge} text="Increment Age" />
<Count label="Salary" value={salary} />
<Button onClick={incrementSalary} text="Increment Salary
</div>
);
}
The incrementAge and incrementSalary functions are recreated every time the
parent re-renders, which causes unnecessary re-renders in the child
components.
🧑🏫
Introduction to Performance Optimization & useCallback Hook
function ParentComponent() {
const [age, setAge] = useState(25);
const [salary, setSalary] = useState(50000);
return (
<div>
<Title />
<Count label="Age" value={age} />
<Button onClick={memoizedIncrementAge} text="Incremen
Key Points:
The Parent component holds the state for age and salary.
// Title.js
import React from "react";
// Count.js
import React from "react";
// Button.js
import React from "react";
dependencies change.
Rendering Title
Rendering Count: Age
Rendering Button: Increment Age
Rendering Count: Salary
Rendering Button: Increment Salary
Rendering Title
Rendering Count: Age
Rendering Button: Increment Age
Rendering Count: Salary
Rendering Button: Increment Salary
Now, only the relevant components re-render when age or salary changes,
significantly improving performance.
Conclusion 🏁
useCallback optimizes performance by memoizing callback functions and
preventing unnecessary re-renders of child components.
Here: The Button component is optimized with React.memo , and the onClick
function is passed as a prop using useCallback to ensure it doesn't get
redefined on each render.
Best Practices 📚
Don't overuse useCallback . It is only useful when passing functions to
optimized child components, and when re-renders of those components
are costly.
Check dependencies carefully. Always pass the state or props that the
callback depends on as dependencies to ensure the memoized function is
updated correctly.
What is React.memo ?
is a higher-order component (HOC) that optimizes performance by
React.memo
For example, if you have a list of items in a parent component and a list item
component that doesn’t change, wrapping the list item component with
React.memo will ensure it only re-renders if its props change.
Usage Example
Without React.memo : The component would re-render every time the parent
re-renders, even if the props haven’t changed.
Syntax
Use it for pure components where the output is solely dependent on props.
Important Notes
Memoization is based on props: React.memo checks if the props of a
component are different. If props haven’t changed, the component won’t
re-render, even if the parent re-renders.
Not useful for state changes: React.memo only checks the props passed
to a component. State changes inside the component will still cause a re-
render.
Example in Code:
function ParentComponent() {
const [age, setAge] = useState(25);
const [salary, setSalary] = useState(50000);
return (
<div>
<h1>Performance Optimization</h1>
<Count label="Age" value={age} />
<button onClick={incrementAge}>Increment Age</button>
<Count label="Salary" value={salary} />
<button onClick={incrementSalary}>Increment Salary</b
utton>
</div>
);
}
In this example:
Summary:
helps to optimize performance by preventing unnecessary re-
React.memo
It’s most useful in pure components where the output depends entirely on
props.
🎥 In this video, we dive into another React hook designed for performance
optimization: useMemo .
1. Introduction to useMemo
In the previous video, we explored the useCallback hook, which optimizes
performance by preventing unnecessary re-renders of functions. Today, we’re
going to look at useMemo , which performs a similar job but focuses on
memoizing computed values instead of functions.
The Problem:
When you click to increment counter1, the function isEven() calculates whether
the number is odd or even, which works fine initially. However, clicking on
counter2 also slows down the UI.
Result: Even though counter2 doesn’t affect the result of isEven() , React
recalculates it, causing delays in UI updates.
Implementation of useMemo
Second Argument: The dependency array. In this case, useMemo will only
recompute the result when counter1 changes. If counter1 doesn’t change,
the previously cached value is used.
When you click counter2, React uses the cached value of isEven without
recalculating, speeding up the UI updates.
useCallback :
Best used when you want to prevent recreation of the function during
every render.
or increment2 .
6. Conclusion
useMemo helps optimize performance by caching computed values and
recalculating them only when dependencies change.
Final Thoughts:
Performance Optimization with Hooks in React helps you ensure that your
components render efficiently, especially when working with complex or
resource-heavy logic. Using hooks like useMemo and useCallback properly can
significantly improve the performance of your application.
What is useRef ?
allows us to persist values across renders without causing re-
useRef
renders.
1. Setup
Create a new file called FocusInput.js .
At this point, we have a basic input field, but no logic to focus it when the page
loads.
useEffect(() => {
inputRef.current.focus(); // Step 3: Focus the input f
ield
}, []); // Empty dependency array ensures this effect run
s only once, when the component mounts
Code Explanation
Step 1:
Step 2:
attribute. This links the inputRef variable to the DOM element, so we can
access it directly.
Step 3:
Summary
allows direct manipulation of DOM nodes in functional components
useRef
Works seamlessly with useEffect for DOM interactions after the component
has mounted.
This is just one of the many use cases of useRef . We'll explore more in-depth
examples in future videos.
Scenario:
We will create a simple form with a TextInput for the user to enter their
username. Upon loading the page, we want the TextInput to automatically be
focused.
Step-by-Step Example
1. Set up the Project:
If you haven't set up a React Native project yet, run the following command
in your terminal:
2. Install Dependencies:
Make sure to have React Native set up correctly on your system with all
dependencies.
return (
<View style={styles.container}>
<Text style={styles.title}>Focus on the input field o
n load</Text>
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 20,
marginBottom: 20,
},
input: {
height: 40,
width: '80%',
borderColor: 'gray',
borderWidth: 1,
marginBottom: 20,
paddingHorizontal: 10,
},
});
Explanation of Code:
Step 1:
Step 2:
component is mounted.
Step 3:
In the JSX, we attach the inputRef to the TextInput using the ref=
component.
Key Points:
useRefis used to access and manipulate the DOM elements or component
instances directly.
Result:
When you run this React Native app, the TextInput will automatically receive
focus when the component is loaded, without any need for additional user
interaction.
This is a basic example showing how useRef works in React Native. You can
extend this pattern to handle animations, scroll-to components, or managing
multiple refs for complex layouts.
In React (JS/TS):
In TypeScript, React typically has the following type for the useRef hook:
inputRef.current!.focus(); // No error
In this case, you are asserting that inputRef.current will always be a valid
HTMLInputElement at the point when focus() is called (i.e., after the component
has mounted).
In React Native:
In React Native, the same concept applies, but TypeScript's nullability and the
DOM abstraction might make you more comfortable asserting that the current
property won't be null in certain scenarios, especially since you often use
useRef for managing components like TextInput , which are guaranteed to be
inputRef.current!.focus();
Here, the ! indicates to TypeScript that the ref will definitely be assigned the
DOM element (e.g., TextInput ), and there’s no need to check for null .
Summary of Differences:
React (Web): Since you are directly interacting with HTML DOM elements,
TypeScript needs to ensure that current is never null before you call
methods like focus() . Without the ! operator, TypeScript flags potential
issues where current could be null .
React Native: React Native doesn't work directly with the DOM but still
manages components similarly. The ! is commonly used to bypass
nullability checks in TypeScript, asserting that after the component mounts,
the current reference will be valid.
Best Practice:
While the ! operator works fine when you are confident about the element’s
existence, it’s good practice to add null checks or guards (like optional chaining
?. ) to ensure safety, especially if the component is not guaranteed to always
render as expected.
Safe Approach:
This approach is typically preferred over using ! because it ensures that the
code won't crash if current is unexpectedly null .
Yes, you can use both the non-null assertion operator ( ! ) and optional
chaining ( ?. ), but each has a different use case and trade-offs:
1. Non-null Assertion ( ! ):
When to use: You should use ! when you are confident that the reference
( current ) will not be null or undefined at that point, often because you know
the component is already mounted.
Example:
2. Optional Chaining ( ?. ):
Optional chaining ( ?. ) is safer and ensures that you only attempt to access
properties (like focus() ) if the object (like inputRef.current ) is not null or
undefined .
When to use: Use ?. when you want to safely handle cases where the
value might be null or undefined (for example, when the component hasn't
mounted yet).
Example:
Benefit: This ensures your code won’t throw errors if the reference is null
at any point.
Use ?. (Optional Chaining) when you want to safely access the property,
especially when there is a chance that the ref could be null or undefined at
Best Practice:
Safe approach (Recommended): Use optional chaining ( ?. ) as it's safer
and avoids potential runtime crashes if the reference is not ready or the
component is unmounted.
inputRef.current?.focus();
Use ! cautiously: Only use non-null assertion ( ! ) when you are sure the
value will always be available (e.g., after component mounts or in specific
lifecycle scenarios).
inputRef.current!.focus();
This makes your code more robust and less prone to errors, especially when
dealing with asynchronous rendering or conditions where refs might not be
immediately available.
Storing mutable variables (like an interval timer) that do not need to trigger
re-renders when changed.
Step-by-Step:
1. Class Component with Timer (Before useRef ):
componentDidMount() {
this.interval = setInterval(() => {
this.setState(prevState => ({ timer: prevState.timer
+ 1 }));
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval); // Clean up on unmount
}
render() {
return (
<div>
Timer: {this.state.timer}
<button onClick={() => clearInterval(this.interva
l)}>Clear Timer</button>
</div>
);
and useEffect .
The timer setup and cleanup are handled in the useEffect hook.
useEffect(() => {
const interval = setInterval(() => {
setTimer(prev => prev + 1);
}, 1000);
return (
<div>
Timer: {timer}
<button onClick={() => clearInterval(interval)}>Clear
Timer</button>
</div>
);
}
useEffect(() => {
intervalRef.current = setInterval(() => {
setTimer(prev => prev + 1);
}, 1000);
return (
<div>
Timer: {timer}
<button onClick={clearTimer}>Clear Timer</button>
</div>
Does not trigger re-renders: Updating intervalRef does not cause re-
renders, unlike state variables.
Useful for handling mutable objects: Any value that needs to be mutable
but does not affect the render cycle can be stored in useRef .
Values stored in useRef persist across renders but do not cause re-
renders when updated.
Holding mutable values that are needed across renders (like interval
IDs, previous state, etc.).
Example: Using useRef to store an interval ID and clear the timer without
causing re-renders.
This example highlights how the useRef hook can be effectively used for
scenarios where we need to store a value (like an interval ID) without affecting
the component's render cycle. This is particularly useful when dealing with
side-effects that don't need to trigger re-renders, such as clearing a timer.
The function's name must start with use (this is a React convention to
identify hooks).
It can call other hooks internally to manage state, side effects, etc.
return {
formData,
handleChange,
resetForm,
};
};
// FormComponent.js
import React from 'react';
import useForm from './useForm';
return (
<div>
<h2>Form with Custom Hook</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Name"
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
<button onClick={resetForm}>Reset Form</button>
</div>
);
};
FormComponent :
Submits the form and logs the form data when the form is submitted.
Key Benefits:
Reusability: The useForm hook can be reused in multiple forms across your
application.
Clean Code: The form logic is encapsulated in the custom hook, making the
component code cleaner and easier to manage.
Calling Other Hooks: Custom hooks can call React’s built-in hooks, such as
useState , useEffect , useReducer , etc.
Summary:
Custom hooks in React are a powerful way to share logic between components.
By creating a function that starts with use , you can encapsulate reusable
functionality and simplify your code. In this example, we created a custom
hook, useForm , to manage form states, which made our form components
1. Introduction:
In this video, we’re creating our first custom hook. To keep it simple, the
custom hook will update the document title based on a counter. In the first half,
we'll implement the logic directly in the component without using a custom
hook. In the second half, we’ll refactor the code to extract the logic into a
reusable custom hook.
// docTitle1.js
import React, { useState, useEffect } from 'react';
Explanation:
Effect ( useEffect ): Updates the document title whenever the count value
changes.
// docTitle2.js
import React, { useState, useEffect } from 'react';
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {c
ount}</button>
</div>
Problem: Both components are repeating the same logic to update the
document title. This duplication isn’t scalable when you have many
components requiring the same behavior.
The hook will take a count value and update the document title.
// useDocumentTitle.js
import { useEffect } from 'react';
Explanation:
Refactored Component 1:
// docTitle1.js
import React, { useState } from 'react';
import useDocumentTitle from './useDocumentTitle';
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {c
ount}</button>
</div>
);
};
Refactored Component 2:
// docTitle2.js
import React, { useState } from 'react';
import useDocumentTitle from './useDocumentTitle';
return (
Explanation:
custom hook.
This eliminates the need to repeat the document title update logic in
each component.
7. Code Walkthrough:
DocTitle1 and DocTitle2 Components:
8. Final Notes:
Custom hooks allow us to abstract reusable logic into a single function and
share it across multiple components.
While this example may seem trivial, custom hooks become much more
powerful as your applications grow and the logic you need to reuse
becomes more complex.
Summary:
We started with two components that duplicated logic for updating the
document title.
🔹 1. Introduction:
In this video, we are going to create a custom hook called useCounter that will
allow us to reuse the logic for a simple counter. We'll break this into two parts:
2. With custom hook: Refactor the logic into a reusable custom hook.
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
Explanation:
JSX: Displays the counter value and buttons to interact with it.
// counter2.js
import React, { useState } from 'react';
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
// useCounter.js
import { useState } from 'react';
Explanation:
// counter1.js
import React from 'react';
import useCounter from './useCounter';
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>Increment</button>
// counter2.js
import React from 'react';
import useCounter from './useCounter';
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
Explanation:
Both components now use the custom hook useCounter to manage their
counter logic.
// useCounter.js
import { useState } from 'react';
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
return (
<div>
<h2>{count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
Explanation:
🔹 6. Final Notes:
Custom Hooks: Allow you to extract reusable logic for managing state and
side effects, making your components cleaner and more maintainable.
Summary:
We added features to the custom hook to allow custom initial values and
step values.
Custom hooks are incredibly useful for managing reusable logic and can be
customized to meet various needs.
1. Without custom hook: Implement a basic form with first and last name
input fields.
2. With custom hook: Move the input logic into a custom hook for better code
reuse.
Code:
// userForm.js
import React, { useState } from 'react';
return (
<form onSubmit={handleSubmit}>
<div>
<label>First Name</label>
<input
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
</div>
<div>
<label>Last Name</label>
<input
type="text"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</div>
<button type="submit">Submit</button>
</form>
);
};
🔹 Breakdown:
State management: Two useState hooks for firstName and lastName .
Test Results:
// useInput.js
import { useState } from 'react';
const bind = {
value,
onChange: (e) => setValue(e.target.value),
};
🔹 Breakdown:
State: A useState hook manages the value of the input.
Reset: A reset function to reset the input value to the initial value.
Binding: The bind object provides value and onChange to connect the input
field.
What It Returns:
1. value: The current value of the input field.
// userForm.js
import React from 'react';
import useInput from './useInput';
return (
<form onSubmit={handleSubmit}>
<div>
<label>First Name</label>
<input {...bindFirstName} />
</div>
<div>
<label>Last Name</label>
<input {...bindLastName} />
</div>
<button type="submit">Submit</button>
</form>
🔹 Breakdown:
Using useInput : We call useInput for both firstName and lastName .
Test Results:
The form works exactly the same as before, but now with less boilerplate
code. The custom hook manages the state and logic, making the form more
maintainable.
There are tons of pre-built hooks out there, but building your own helps
you understand the magic of React hooks!
6. Conclusion 🏁:
We built a simple user form with controlled components, then refactored it
using a custom hook ( useInput ). Here's what we achieved:
Key Takeaway: Custom hooks make managing logic much easier, leading to
cleaner and more scalable React apps! 👏
Problem:
Let's say we want to fetch data from a server (e.g., a list of users) and handle
the loading, error, and success states. This logic might be reused across
multiple components, so we create a custom hook for it.
Step-by-Step Guide:
// useFetch.js
import { useState, useEffect } from 'react';
import { Alert } from 'react-native';
fetchData();
}, [url]); // Re-run the effect when the URL changes
🔹 Breakdown:
States:
Fetching Data:
Returning Values: The hook returns the data , loading , and error states,
which can be used by the component to display relevant information (like
loading indicators or error messages).
// UserList.js
import React from 'react';
import { View, Text, FlatList, ActivityIndicator } from 're
act-native';
import useFetch from './useFetch'; // Import the custom hoo
k
if (loading) {
return (
<View style={{ flex: 1, justifyContent: 'center', ali
gnItems: 'center' }}>
<ActivityIndicator size="large" color="#0000ff" />
<Text>Loading...</Text>
</View>
);
}
if (error) {
return (
<View style={{ flex: 1, justifyContent: 'center', ali
gnItems: 'center' }}>
return (
<View style={{ flex: 1, padding: 20 }}>
<FlatList
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View style={{ marginBottom: 15, padding: 10, bac
kgroundColor: '#f0f0f0', borderRadius: 5 }}>
<Text>{item.name}</Text>
<Text>{item.email}</Text>
</View>
)}
/>
</View>
);
};
🔹 Breakdown:
Calling the Custom Hook: We call useFetch with the API URL
https://jsonplaceholder.typicode.com/users to fetch a list of users.
Handling States:
Data State: Once the data is fetched successfully, we display the list of
users using a FlatList .
Once the data is fetched successfully, it will display the list of users.
Additional Ideas 💡:
Add Retry Logic: Enhance the hook to support retrying failed requests
automatically.
Now you have a powerful custom hook in React Native that can easily be
reused across your app!
Great observation! In React and React Native, you can return either an object
or an array from a custom hook. The approach you choose depends on how
you want to structure the returned values. Let's break it down:
Why an Object?
Easier to extend: If you want to return more values in the future (like status
or message ), it's easy to add them to the object without changing the way
consumers access the hook.
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
// Returning an object
return { data, loading, error };
};
In the component:
// UserList.js
import React from 'react';
import { View, Text, FlatList, ActivityIndicator } from 're
act-native';
import useFetch from './useFetch';
return (
<FlatList
data={data}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
);
};
Why an Array?
Familiarity with useState : Since useState returns a state variable and its
updater function as an array (e.g., [state, setState] ), using arrays in custom
hooks can feel intuitive.
// Returning an array
return [count, increment, decrement, reset];
};
In the component:
// Counter.js
import React from 'react';
import { View, Text, Button } from 'react-native';
import useCounter from './useCounter';
return (
<View>
<Text>Count: {count}</Text>
Important Takeaways:
Object Return is more flexible and makes the code more readable when
there are multiple return values.
Array Return is great for simpler hooks with a fixed number of return
values, especially if you follow the pattern of useState or useReducer .
Choose what makes the most sense for your hook's purpose! ✨
Now you can make custom hooks both clean and scalable. 🌱
The names count , increment , decrement , and reset make it immediately clear
what each value or function represents. This is self-documenting code.
Vs. an array:
While this works, it's less intuitive because the names of each item in the
array are not immediately apparent.
Adding more items may lead to confusion when the array grows.
If you change the order of items in the returned array later, you'll need to
update every destructured variable throughout your app.
💡 Example:
return { count, increment, decrement, reset, timestamp }
You can add more functionality over time (like timestamp ), and the rest of the
code won't need to change, keeping the hook backward-compatible.
💡 Example:
return { count, increment, decrement, reset }
Each key clearly explains the function of the value or method returned. This
self-documents the hook and makes it clear for others (or your future self)
what the return values are.
Here, you can quickly see what each custom hook returns, and it's easier to
manage multiple hooks without confusion.
🏆 Summary of Benefits:
Readability: Objects give descriptive names to values, making the code
more readable.
Scalability: Objects grow naturally, making the code more flexible for future
changes.
You plan to extend the hook with more functionality over time.
So, while arrays are great for simpler cases (like useState ), objects are a more
robust, scalable, and user-friendly choice when building custom hooks in
React and React Native! 😎✨