What are hooks
Hooks are a new feature added in React V16.8. They allow you to use state and other React features without writing class components.
Hooks are backward-compatible i.e Hooks don’t contain any breaking changes.
Previously you could use states only within a class component.
With the introduction of hooks, you can pretty much ignore classes altogether.
Hooks don't work in Class Component
Why Do we need Hooks
- Code that's easy to reason, ended up having a lot of boilerplate such as constructor initialization and binding functions. Using Hooks we can simplify it to half the lines of code, achieving the same results
- Although react has its own state management library such as redux, the introduction of the createContext hook provided a simpler interface for sharing states across components in a much simpler manner.
- If you start out as a functional component and the complexity ends up increasing, it's just a matter of time before you notice you'll need states and decide to refactor it into a class component. With React Hooks, since functional components have the capability of accessing and modifying the state, the refactoring effort will be minimal.
- As a beginner, one of many challenges that I've faced is understanding "this". So you can imagine my happiness when I learned that when it comes to React Hooks, you don’t have to worry about "this" at all.
- forgetting to use binding on methods and wondering why my code wasn't working for hours is another problem that Hooks solves. Just write the function and call as a mormal programmer!
- With the class-based approach, we have different life cycle methods such as componentDidMountand componentDidUpdate. The problem with this is we cant keep related logic together. In some instances, we might have to call fetch() and initialize event handlers in componentDidMount. Thanks to hooks we can separate related logic quite well. -Last but not least we can create custom hooks, which can hold repeated code, thereby reducing unnecessary code repetition.
Approach
In this article, we are gonna talk about the most commonly used hooks
- useState
- useEffect
- useCallback
- useRef
useState
hook-name: useState
syntax: const [state_name, setState] = useState(0);
explaination:state_name is the name of the state, while setState is a method that takes in a value that it sets as a new state_name value. eg setState(new_state_value)
The concept behind that syntax is array destructuring. if you dont know what destructuting is, its basically a JavaScript expression that allows you to unpack values from objects.
In simple words it means hey, I'm first gonna take a property from useState and then I'm gonna take a function from useState. Internally setState holds these properties and methods which we can use to create and access those states.
the value passed through useState is the initial value of the state.
code:
Declaring a State Variable
const [count, setCount] = useState(0);
Reading State variable
<p>You clicked {count} times</p>
Updating State
<button onClick={()=>setCount(count+10)}>Add 10</button>
sideNote:You can access the previous state of a state by using
<button onClick={()=>setCount(prevCount=>prevCount+10)}>Add 10</button>
useEffect
hook-name: useEffect
syntax:useEffect(callback, dependencies)
explaination:
useEffect() is for side-effects.
Side-effects are basically calculations that dont target the output value, eg: fetch request, timer functions etc
As we know the components side-effect and render are different. So how do we seperate these both!?..
We use useEffect!!!
useEffect accepts 2 parameters i.e callback and the dependencies
- callback is the callback function containing side-effect logic. useEffect() executes the callback function after React has committed the changes to the screen. By now you must have realized that useEffect is a substitute to the lifecycle methods of a class component!
- dependency is an optional array that determines when should the callback function be executed.
Lets Talk dependencies dependencies basically let you control when the side-effect runs.
The following are test cases observing different dependencies. - When the dependencies are not provided
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
as you can see the second parameter is not present. In this case, the useEffect executes after every render. This is pretty much similar to the componentDidUpdate part of the react class lifecycle. If you don't know about it. It's even better. hooks pretty much-replaced class components.
- When the dependency is just an empty array
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
as you can see the second parameter is an empty array. In this case, the useEffect executes only once after the initial render. This is pretty much similar to the componentDidMount part of the react class lifecycle.
- When the dependency has a state value
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}
the side-effect runs only when the dependency value in the array present in the second parameter changes.
When the useEffect returns a function
some side-effects need cleanup such as closing a socket or clearing a timer. This is similar to the componentsDidUnmount part of the react class lifecycle
function MyComponent() {
useEffect(() => {
return () => {
//the Cleanup code goes here..
};
}, []);
useContext
hook-name:useContext
explaination:
Data can be passed down from parent component to child component through props, which makes the middle components unnecessarily hold props value, or we use the reacts library called redux.
In the above figure, you'll see how data is passed through each component before it reaches its destined component. Although react has its own state management library called Redux, the same can be achieved by using the useContext hook in a much-simplified manner.
useContext is basically defined and used in 3 steps
- Defining the context variable
The context variable should be defined above the functional component, in the parent component
const UserContext = React.createContext();
- saving props to pass down to its child component
In the above code, we can see "propsValue" is passed to the "UserContext" variable. And can be accessed by any of its lower levels.<UserContext.Provider value={'propsValue'}> <ComponentC/> </UserComponent.provider>
below is a code snippet of the parent file
- using the context variable
To use the values we first import useContext and the providers which were exported to which the value was assigned. After importing we pass the value to the useContext Hook and assign the returning value to a variable that can be used later.
useCallback
hook-name:useCallback
code and explaination:
import React, { useState, useCallback } from 'react'
const [age, setAge] = useState(18);
const [salary, setSalary] = useState(10000);
const incrementAge = () => {
setAge(age + 1)
}
const incrementSalary = () => {
setCount(salary +1000)
}
return (
<>
<Title/>//some randon text
<Count text='Age' count={age}/>
<Button handleClick={incrementAge}/>
<Count text='salary' count={salary}/>
<Button handleClick={incrementSalary}/>
</>
)
}
If you run the above code all the Components will be rendered and changes to one component will result in all the other sibling components being rerendered.
As the complexity of the application increases the performance will reduce. To prevent this from happening we can use React.memo.
React.memo basically saves the result of a Componentin cache memory. On rerendering, If there is no change in the props or state of the component it returns the saved result from the cache, thereby eliminating unnecessary renders.
Syntax=>" while exporting the component wrap it in React.memo"
export default React.memo(ComponentName)
Although this method will prevent the Component from rendering the count and button would still rerender!
Now the question is why is that?
every time this component rerenders, the function incrementAge and incrementSalary are created again thereby the child component who reference these components believe that they are new functions, which ultimately make React.memo useless.
Now the question is how do we prevent these functions from rendering again and again?
The Answer>> useCallback Hooks.
The useCallback hook is similar to React.Memo, but in this case, the function will rerender only if its dependencies have changed. If not, it will return the function that was saved in cache memory. thereby preventing unnecessary renders.
This is useful when passing function references to the child component that uses React.memo to know if render is necessary or no.
Why not use useEffect?
useEffect() runs after the first render. So if there are any references made to that function it won't be accessible by the child component. thereby throwing an error. useCallback on the otherhand executes before the first render.
syntax:
const incrementAge = useCallback(() => {
setCount(age + 1)
}, [age])
const incrementSalary = useCallback(() => {
setCount(salary - 1)
}, [salary])
Only when the dependencies change, the functions rerender.
useRef
hook-name: useRef
syntax and explaination:This hook helps us access dom nodes directly within functional components.
These are the following steps we take to write out useRef.
- Create a ref
const inputRef=useRef(null);
- attach to input element
<input type='text' ref={inputRef} />
- call what you want to do with useRef
useEffect( ()=>{ inputRef.current.focus() },[])
All useRef is, is basically a pointer that points to the node you want to manipulate.
code snippet: