Hooks
States
State is a JavaScript object that stores a component's dynamic data and determines the component's behavior. It is managed internally by the component itself and can only be updated by the component itself. State is used to store data that can change over time, such as user input, network responses, and UI events.
State before hooks
Yes, before the introduction of hooks in React, components could still have state. Stateful components were commonly created using class-based components, where state was managed through the this.state property. Class-based components had a constructor method to initialize the state and various lifecycle methods (e.g., componentDidMount, componentDidUpdate, etc.) to handle state updates and side effects.
import React, { Component } from "react";
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
incrementCount() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.incrementCount()}>Increment</button>
</div>
);
}
}
While class-based components were widely used, they sometimes led to complex and less reusable code. Hooks were introduced as a way to simplify state management and side effects in functional components, making it easier to work with state and promoting better code organization and reusability.
If we have const x = 10;
is that a state too ?
No, const x = 10; is not considered state in the context of React components.
In React, "state" refers to any data that can change over time and affects the rendering of a component. It is used to manage dynamic values that can be modified and trigger re-rendering of the component when they change.
The code const x = 10; is simply assigning the value 10 to a constant variable x, which means x has a fixed value of 10 throughout the component's life cycle. This is not considered state because it doesn't change during the component's rendering or interaction with the user.
In contrast, React state is typically used when you want to track and update data that can change in response to user actions, network requests, or other events. This dynamic behavior is what makes state essential for building interactive and responsive user interfaces.
Component Lifecycle Method
In React, the term "lifecycle" refers to the different stages that a component goes through from its creation to its removal from the DOM. During each stage of a component's lifecycle, React provides developers with specific methods that can be overridden to perform actions or handle behaviors related to that particular stage.
The React component lifecycle can be divided into three main phases:
Mounting Phase:
This is the initial phase when a component is being created and inserted into the DOM. It involves methods that are called when the component is being mounted. Lifecycle methods in this phase are called in the following order: constructor()
, static getDerivedStateFromProps()
, render()
, and componentDidMount()
.
Updating Phase:
This phase occurs when a component's props or state change, causing it to re-render. It involves methods that are called when the component is being updated. Lifecycle methods in this phase are called in the following order: static getDerivedStateFromProps(), shouldComponentUpdate(), render(), getSnapshotBeforeUpdate(), and componentDidUpdate().
Unmounting Phase:
This phase occurs when a component is being removed from the DOM. It involves methods that are called when the component is being unmounted. The only lifecycle method in this phase is componentWillUnmount(), which allows developers to perform cleanup tasks before the component is removed. Additionally, there is a newer error handling phase introduced in React 16.
Error Handling Phase:
This phase is related to handling errors that occur during rendering, lifecycle methods, or inside child components. It involves two error handling methods: static getDerivedStateFromError() and componentDidCatch(). It's essential to understand the component lifecycle because it provides developers with opportunities to perform specific tasks at different stages of a component's existence. However, with the introduction of React Hooks, the traditional class-based lifecycle methods are being replaced in favor of using functional components with hooks like useState, useEffect, etc. These hooks provide a more straightforward and predictable way to manage component behavior without relying on class components and their lifecycle methods.
useState
useState is a React Hook that lets you add state to functional components. It takes an initial value as an argument and returns an array of two values: the current state and a function to update it.
import React, { useState } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
return (
<div>
<div>Count: {count}</div>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
In this example, the count state is created using the useState hook. The count state is initialized to 0, and the setCount function is used to update the count state. When the button is clicked, the count state is updated using the setCount function, which causes the component to re-render.
useEffect
UseEffect in code is used mostly for side effects. Whenever something or the other changes we will be using useEffect to do somethhing else based on the code change. It will run atleast once when the component mounts with the code that it has inside of it's body.
import React, { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("useEffect called");
}, [count]);
}
Here as you can see in this example the useEffect is now hooked to the count and as soon as the count will increase we will be console logging it that is because we have count in the dependency array.
Let us also understand cleanup
import React, { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("useEffect called");
return () => {
console.log("cleanup function called");
};
}, [count]);
}
Now the output of this code will be cleanup function called
and then useEffect called
because the cleanup function is called before the useEffect is called. Why does this happen ?
The useffect will run once at component mount and then it will run everytime the dependency array changes. But when the dependency array changes the cleanup function is called first, then the useEffect destroys the previous effect and then it runs the useEffect again with the new value.
useMemo
The useMemo()
is a React Hook that lets you cache the result of a calculation between re-renders. This can be useful when you have a calculation that is expensive to perform, such as a complex algorithm or a database query.
import React, { useMemo } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
const result = useMemo(() => {
// Perform expensive calculation here
return count * 2;
}, [count]);
return (
<div>
<div>Result: {result}</div>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
This will be called only when the count state changes & when the component is re-rendered.
The useMemo hook is used to memoize a value and recompute it only when its dependencies change. It takes a function and an array of dependencies as arguments. The function is executed initially and whenever any dependency in the array changes. The result of the function is stored and returned as the memoized value.
useCallback
In React, useCallback
is a hook that is used to memoize a function so that it is only re-created when one of its dependencies changes. This is similar to useMemo, but instead of memoizing a value, useCallback memoizes a function.
import React, { useCallback } from "react";
function MyComponent({ value1, value2 }) {
const handleClick = useCallback(() => {
console.log("Clicked!");
}, [onClick]);
return <button onClick={handleClick}>Click me</button>;
}
In this example, the handleClick function is memoized using useCallback. This means that the function will only be re-created when the onClick prop changes.
See everytime a component re-renders, all of its functions are re-created. This is because functions are objects in JavaScript, and objects are created every time they are referenced.
UseCallback will preserve the identity of the function between re-renders, so that it is only re-created when one of its dependencies changes. This can be useful when you have a function that is expensive to create, such as a function that performs a complex calculation or a database query.
useReducer
useReducer is a hook for state management, much like useState, and relies upon a kind of function called a reducer.
Reducers btw are just pure functions that take the previous state and an action, and return the next state.
useReducer can be used in many of the same ways that useState can, but is more helpful for managing state across multiple components that may involve different operations or "actions".
You will need to reach for useReducer less than useState around your app. But it is very helpful as a powerful means of managing state in smaller applications, rather than having to reach for a third-party state management library like Redux.
import React, { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, { count: initialCount });
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
In this example, the useReducer hook is used to manage the state of the count variable. The reducer function is passed to the useReducer hook as the first argument, and the initial state is passed as the second argument.
useContext
useContext is a React Hook that lets you read and subscribe to context from your component. It's similar to the useState hook, but instead of managing state, it lets you access context from anywhere in your component tree.
Differences
useRef VS useState
The useRef hook is similar to the useState hook, but it does not cause the component to re-render when the ref object is updated.
import React, { useRef, useState } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
const ref = useRef();
return (
<div>
<div ref={ref}>Hello</div>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
In this example, the ref object is created using the useRef hook. The ref object is then passed to the div element, which allows us to access the DOM node of the div element using the ref.current property.
The count state is created using the useState hook. When the button is clicked, the count state is updated using the setCount()
function, which causes the component to re-render.
useMemo VS useCallback
useMemo and useCallback are both hooks in React that help optimize the performance of functional components by memoizing values or functions. Let's break down their differences in simple terms:
useMemo: This hook is used to memoize a value and recompute it only when its dependencies change. It takes a function and an array of dependencies as arguments. The function is executed initially and whenever any dependency in the array changes. The result of the function is stored and returned as the memoized value.
import React, { useMemo } from "react";
const MyComponent = ({ a, b }) => {
const sum = useMemo(() => {
console.log("Computing sum...");
return a + b;
}, [a, b]);
return <div>{sum}</div>;
};
In this example, the sum value is memoized using useMemo. The function inside useMemo computes the sum of a and b. If either a or b changes, the function will recompute the sum and update the memoized value. Otherwise, it will use the previously memoized value without recomputing.
useCallback: This hook is used to memoize a function and return the same instance of the function until its dependencies change. It takes a function and an array of dependencies as arguments. The function is memoized and returned as a callback. The callback will remain the same unless any of the dependencies change.
import React, { useCallback } from "react";
const MyComponent = ({ onClick }) => {
const handleClick = useCallback(() => {
console.log("Button clicked!");
onClick();
}, [onClick]);
return <button onClick={handleClick}>Click me</button>;
};
In this example, the handleClick function is memoized using useCallback. The function is defined inline and will be memoized. If the onClick dependency changes, the memoized function will be updated. Otherwise, it will return the same function instance, preventing unnecessary re-renders of child components that receive handleClick as a prop.
To summarize, useMemo is used to memoize a value and recomputes it when dependencies change, while useCallback is used to memoize a function and return the same function instance unless dependencies change. Both hooks help optimize performance by preventing unnecessary re-computations or re-renders.
Virtualization
Virtualization is a technique used to render only the components that are currently visible on the screen. Instead of rendering the entire list of components, React renders only the ones in the current view. When dealing with long lists or large datasets, rendering all components at once can lead to performance issues. Virtualization helps in improving performance by rendering only the components that are needed, based on the user's scroll or view.
Reconcialiation
Reconciliation is the process through which React updates the UI to match the most recent state of the components. React compares the new virtual representation of the component tree with the previous one and makes the necessary updates to reflect the changes. Efficient reconciliation ensures that the UI stays in sync with the application state without causing unnecessary re-renders. This is crucial for maintaining a smooth user experience and optimal performance.
How It Works:
- Diffing Algorithm: React uses a diffing algorithm during reconciliation. It compares the new and old virtual trees, identifying the differences or changes.
- Minimal Updates: React aims to make the smallest number of updates necessary to bring the UI to the desired state. It doesn't re-render the entire component tree but only the parts that have changed.
In summary, virtualization helps in rendering only what's necessary for the current user view, and reconciliation ensures that the UI is efficiently updated based on changes in the component tree. Together, they contribute to creating fast and responsive user interfaces, especially in applications dealing with dynamic or large datasets.
Higher Order Component
In React, a Higher Order Component (HOC) is a pattern that allows you to reuse component logic by wrapping a component with a function. The HOC takes a component as an argument and returns a new component with additional props or behavior.
import React from "react";
const Message = ({ text }) => {
return <div>{text}</div>;
};
export default Message;
Now, we want to create a Higher Order Component that adds a timestamp to the message and passes it as a prop to the original Message component.
import React from "react";
const withTimestamp = (WrappedComponent) => {
return (props) => {
const timestamp = new Date().toLocaleString();
return <WrappedComponent {...props} timestamp={timestamp} />;
};
};
const Message = ({ text, timestamp }) => {
return (
<div>
<p>{text}</p>
<p>Timestamp: {timestamp}</p>
</div>
);
};
const MessageWithTimestamp = withTimestamp(Message);
export default MessageWithTimestamp;
In this example, we have created the withTimestamp
Higher Order Component that takes the Message component as an argument and returns a new component. The new component has the original Message component as a child but with an additional timestamp prop.
Now, wherever you want to use the Message component with the timestamp feature, you can use MessageWithTimestamp
instead:
import React from "react";
import MessageWithTimestamp from "./MessageWithTimestamp";
const App = () => {
return <MessageWithTimestamp text="Hello, world!" />;
};
export default App;
Now, when rendering the App component, it will display the message with the added timestamp:
Hello, world!
Timestamp: 7/31/2023, 12:34:56 PM