React hooks - useState, useEffect, useContext , and useRef
Table of contents
Introduction
In the last two blogs, I explained everything about the most important but lesser-used hooks for performance improvement of the application. In this blog, I am going to talk about the commonly used React hooks that you will find in every React application - The useState, useEffect, useRef, and useContext. So let's dive in!
useState
useState allows React components to have a state and is a recommended way of managing states within a functional component. The useState hook returns an array consisting of two elements within it. The first element is the state variable and the second is a function that updates the state variable.
Let us look at an example to understand how it is used.
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0); //the useState hook
return (
<>
<button onClick={() => setCount(count + 1)}> + </button>
<div> { count } </div>
<button onClick={() => setCount(count - 1)}> - </button>
</>
);
}
In the example above, I have implemented a simple counter. As you can see, when the button is clicked, the "setCount" function updates the count state.
One must remember that updating the state causes a component to re-render. Every time a component renders, an initial state computation takes place which is very slow and so to prevent it from happening with every rendering of a component, we can restrict the initial state component down to just the first rendering of a component by passing a function as an argument to the useState hook. The example below demonstrates the implementation of the above concept.
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(() => {
return 0;
}); //passing in a function as an argument to the useState hook
return (
<>
<button onClick={() => setCount(count + 1)}> + </button>
<div> { count } </div>
<button onClick={() => setCount(count - 1)}> - </button>
</>
);
}
useEffect
useEffect hook allows us to implement any side effects every time the component renders. Suppose, whenever we want to change the state of a component, and let's say every time our state changes, we want to fetch data from an API, then we can do this using the useEffect hook. Anything within the useEffect will be executed every time the state changes. The side effects in the useEffect are run every time the component renders.
Also, the useEffect hook takes a list as an argument. This list of arguments is compared with the list of arguments in the previous rendering of the component, and if any changes are detected, then the side effects are run.
Let us look at an example.
import axios from "axios";
import { useEffect, useState } from "react";
export default function FetchData({ url }) {
const [data, setData] = useState([]);
useEffect(() => {
axios.get(url)
.then((res) => setData(res))
.catch((err) => console.log(err));
}, [url]);
return (
<>
<div>
{ data }
</div>
</>
);
}
In the above example, I am simply fetching some data from the API, and storing it in the "data" state variable. Function "FetchData" takes in a prop called "url", and useEffect has been provided with "url" as its argument such that every time url changes, the side effects in the useEffect will be run. In the example above, the side effect is nothing but fetching data from the API.
In a scenario where you wish to provide an empty list as an argument, would result in the side effect being run only once during the initial rendering of the component.
import axios from "axios";
import { useEffect, useState } from "react";
export default function FetchData({ url }) {
const [data, setData] = useState([]);
useEffect(() => {
axios.get(url)
.then((res) => setData(res))
.catch((err) => console.log(err));
}, []);
return (
<>
<div>
{ data }
</div>
</>
);
}
In the example above, the data will be fetched only once when the component renders for the first time. Any subsequent rendering won't run the side effect.
useContext
The typical way of passing some data from one component to another is via props.
Let's look at how we pass props from one parent component to another.
import { useState } from "react";
export default function App() {
const [index, setIndex] = useState(0);
return (
....
<List index={index} /> //passing down the prop to the List component
);
}
export default function List({ index }) { //accessing the prop in the List component
return (
....
);
}
This is all fine and good if the props have to pass from the parent component to the immediate child component. However, this is not the case all the time. If we represent all the components in a React app in a tree structure, with the root representing the parent component and all the other nodes as the child components, then to pass a prop from the parent component to a child component which happens to be the leaf node of the tree, the prop has to pass through all the nodes in between them. This phenomenon is known as prop-drilling, and as a result of this, all the other components which don't need that prop have access to that prop as well. Hence, it is not an useful way of writing clean, reusable, and DRY code.
The useContext hook allows us to avoid this issue. With the useContext hook, we can pass down a prop from the parent component to the child component without having to pass it down through intermediate components.
Let us take a look at an example to understand how useContext works.
//Defining the context in here.
import { useContext, createContext } from "react";
const context = createContext(null);
export default function Provider({ children }) {
const [index, setIndex] = useState(0);
return (
<context.Provider value = {{ index, setIndex }}>
{ children }
</context.Provider>
)
}
export { Provider, context }
//The index.js
const root = React.creatRoot(document.getElementById("root"));
root.render(
<Provider>
<App />
</Provider>
)
//The App.js
import { context } from ".src/Provider.js";
export default function App() {
const { index, setIndex } = useContext(context); //Getting the values passed on the App component by the Provider
//index and setIndex can now be used anywhere in the App component
return (
....
)
}
useRef
The useRef hook is an interesting one because it works similar to the useState hook i.e. the value stored by the object returned by the useRef hook persists between re-renders, just like the state variable returned by the useState hook. The major difference is that updating the value of the useRef hook doesn't result in rendering of the component.
The useRef hook is mostly used for targeting DOM elements, or to store mutating values that needn't require to cause a rendering of the component.
The object returned by the useRef hook has only one property which is the "current" property which holds the value passed down to the useRef hook as input.
Let us look at an example to learn about how to use the useRef hook.
import { useRef } from "react";
export default function App() {
const ref = useRef(null);
// this means the ref.current = null;
}
The most common use case of useRef hook is to focus on an element. Let's look at an example.
import { useRef } from "react";
export default function App() {
const focusRef = useRef(null);
const focusUp = () => {
focusRef.current.focus();
}
return (
<>
<input ref={focusRef} type="text" placeholder="please type something in here" />
<button onClick={focusUp}> focus up! </button>
</>
)
}
In the above example, every time the button is clicked the focus will be on the input text field for the user to type into without having to click on the input field first.
Final Conclusion
That's all about the useState, useRef, useContext, and useEffect hooks. In my next blog, I will tell you about creating your very own custom hooks! Till then, buh-bye!
You can follow me on Github, LinkedIn.
Thank you for reading.