Hooks in React
The hooks useEffect(), useRef, useState and useLayout(), if you don’t know use them well, your life could be tiresome as a react or react native developer.
In this tutorial I will cover how to use useEffect(), useRef, useState and useLayout() in depth. I will also show you example cases with real-world code.
See the index of this tutorial. you can jump to any subtopic you like
Use useState() and useEffect() together
‹––––––––––––––––––––––––––––––––––––––›
All about useEffect() hook
What is useEffect()?
The hook useEffect serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API
First thing you have to know that
√ The hook useEffect() gets called automatically right after render or rerender.
√ You can use useEffect() only in function component
The hook useEffect() takes two arguments.
useEffect(function(), [dependencies])
The first one is a function where we run some logic and the second one controls how many times useEffect() would get called.
How to use useEffect()?
There are three ways to use useEffect() hook based on dependencies
A. First way without any dependencies
import { useEffect } from 'react';
function DoComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
You can see from the above code that, nothing in the second argument. When we don’t pass anything to dependencies, then it would be an empty argument. Without any dependencies(means passed nothing as a second argument), useEffect() gets called every time after render happens.
√But no parameter causes infinite loop. Most of the time you want to avoid them.
B. Second way with an empty array
import { useEffect } from 'react';
function DoComponent() {
useEffect(() => {
// Runs only once after the initial render
}, []);
}
Since we are passing an empty array, this time useEffect() will only get called once.
C. Third way by passing props or state variable
import { useEffect, useState } from 'react';
function DoComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}
In the above case our dependencies are prop or state values. When any of the prop or state values change, useEffect() will get called each time.
When do you use useEffect() ?
First, you have to understand a concept called side effect. What is side effect in programming or in react naitve?
A "side effect" is anything that affects something outside the scope of the function being executed. It means if you run a block of code and it creates effect outside the scope of the code block, we can say this is a side effect.
For example, if you call a network request after that code block gets executed, there are many other things that happen like logging, updating data, cache data.
So whenever you have side effects in your code you should use useEffect() and put the block of code inside the useEffect(), that causes the side effect.
More side effect example like DOM manipulating, the global variables being changed by a function call, timer functions like setTimeOut.
But you need to remember that function rendering and side effects are independent. So they should be handled separately.
Clean up with useEffect
If you have timers, listernes or socket connection in useEffect hook, you should also clean them up them useEffect. Otherwise they will cause memory leak. See the code below
useEffect(() => {
const timer=setTimeout(() => {
...................................
...................................
}, 1000);
// Clear timeout if the component is unmounted
return () => clearTimeout(timer);
});
In the above function, we must return clearTimeout(), otherwise it will cause memory leak because of timer variables.
Let's see another example, if you have socket connection in your code
useEffect(() => {
const socket = socketIOClient(ENDPOINT);
socket.on("FromAPI", data => {
setResponse(data);
});
return () => socket.disconnect();
}, []);
We must return socket disconnet object. It makes sure, when the component unmounts, the connection will close.
All about useRef()
The hook useRef() is another easy hook to use. It accepts one argument for initialization and returns a reference of the value.
const val = useRef(0);
The value it accepts is mutable means, we can change it later, to change the value we can use the special property called "current".
You can change the value of the variable like
val.current = 10
Now val will hold value 10. The new value will be available without rerendering of the component. This value is accessible for the full life cycle of React component. See the example below
import React , {useRef}from 'react';
import {View, StyleSheet, Text, TouchableOpacity} from 'react-native';
function UseRefHook(props) {
const countRef = useRef(0);
const handle = () => {
console.log("Clicked ", countRef.current, "times");
countRef.current++;
};
console.log('I rendered!');
return (
<View style={styles.container}>
<TouchableOpacity onPress={handle}>
<Text>
Click
</Text>
</TouchableOpacity>
</View>
);
}
const styles=StyleSheet.create({
container:{
flex:1,
justifyContent:"center",
alignItems:"center"
},
});
export default UseRefHook;
The above code first outputs "I rendered!", and you see this output once. If you keep clicking on the button, you will see below log
I rendered!
Clicked 1 times
Clicked 2 times
Clicked 3 times
.........................
√ Even though each time the countRef.is updated, the component is not rerendered.
We can confirm that by adding below code snippet before handle() onPress handler.
.................................
useEffect(() => {
console.log("useEffect is called");
});
const handle = () => {
countRef.current++;
console.log("Clicked ", countRef.current, "times");
console.log("handled");
};
.................................
We will see that, the log output "useEffect is called" is not shown on the console. Because useEffect() should have been called if the rerender of the component happened. But even though count.Ref value changed rerender did not happen, thus useEffect() did not get called.
All about useState
What is useState()?
At the heart of React is that, components react to state and prop change. And we use useState() hook to function component.
There is a big difference between useRef() and useState(). The hook useRef() change values does not cause render of react native. But the value of useState() causes render of react component.
Let's see how to initialize and use useState
const [val, setVal] = useState(0);
When we initialize it with 0(any value is ok), then useState return an array of objects. Then we can do array destructrue, then we named them val and setVal. The first object val contains the initial value assigned to it. And the second object setVal is a function which we can use to change the value.
So
√val is state variable now
√setVal is a function
The value val is accessible from anywhere in the component. And the latest value is found during component rendering.
You could also declare it like below
const valState = useState( 0);
const val = valState[0]; // Contains 0
const setVal = valState[1]; // It’s a function
See how change of state variable causes rendering
How to use useState()
See the below example
import React, {useState} from 'react';
import { TouchableOpacity, Text, View, StyleSheet } from 'react-native';
function Hooks(props) {
const [val, setVal] = useState(0);
const handlePress=()=>{
setVal(val+1);
}
console.log("rendered ", val);
return (
<View style={styles.container}>
<TouchableOpacity onPress={handlePress}>
<Text>{val}</Text>
<Text>
Press me
</Text>
</TouchableOpacity>
</View>
);
}
const styles=StyleSheet.create({
container:{
justifyContent:"center",
alignItems:"center",
flex:1,
},
});
export default Hooks;
In the above function on handlePress we are changing the state of the component. On other words, we are changing the state variable value
setValue(val+1);
The above line will cause render of the component.
Then you will see the output on the console like
rendered 0
rendered 1
rendered 2
rendered 3
rendered 4
rendered 5
.................
If your state variable is Animated and initialized using useState() then you have to use setValue() to reset it.
const [anim, setAnim]=useState(new Animated.Value(0));
To reset the value for the above variable we need to use
anim.setValue(resetValue);
See how to get the most recent value in react native
To get the most recent value you must use second argument returned by useState hook and pass a increasing or decreasing counter or a new string value.
The increasing counter can increase any value and decreasing counter can decrease any value. Otherwise you can also use a string value
Use useState() and useEffect() together
Let's see in real example how to use useState() and useEffect() together.
import React, { useEffect, useState } from 'react';
import { Text, View, StyleSheet, ActivityIndicator, FlatList} from 'react-native';
export default function StateAndEffect() {
const [showData, setShowData] = useState([]);
const load =async()=>{
console.log("I am loading now....")
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const json = await response.json();
console.log("State variable is doing to change now...")
setShowData(json); //state variable changed. it will cause render of the component
console.log("I am done loading...")
}
useEffect(()=>{
load();
console.log("I am useEffect...");
},[])
console.log("I am rendering...")
return (
<View style={styles.container}>
{
showData.length > 0 ?(
<View style={{paddingTop:100}}>
<FlatList
data={showData}
keyExtractor={(item)=>item.title}
renderItem={({item})=>
<View style={{margin:10}}>
<Text>{item.title}</Text>
</View>
}
/>
</View>
):(
<View style={styles.buttonContainer}>
<ActivityIndicator />
</View>
)
}
</View>
)
};
const styles = StyleSheet.create({
buttonContainer:{
backgroundColor:"#fc5c65",
padding:10,
width:100,
borderRadius:50,
alignItems:"center",
justifyContent:"center",
alignItems:"center",
alignSelf:"center",
top:"50%"
},
container:{
flex:1,
backgroundColor:"white"
},
});
In the above function, we use useState and useEffect together.
showData is initialized to null using useState([]), and at the initial render we see react native activity indicator is showing loading indicator. The hook useEffect() runs only once since the second arugemnt is an empty array. It calls the function load() which fetches data from remote server.
Until the data has been fetched we see the activity indicator. Once the data load has been done, showData has been changed using setShowData function, which is part of state created earlier. Since the state is set to data array using setShowData(json), the render happens again and we the data in flatList.
You will see the output log "I am rendering ..." one time fist and then see again once the indicator has gone, which proves that, change of useState() creates render of the component. See the log for this
Finished building JavaScript bundle in 177ms.
Running application on iPhone 11.
I am rendering...
I am loading now....
I am useEffect...
State variable is doing to change now...
I am rendering...
I am done loading...
Click to see another example here about useState and useEffect.
See another example
import React, { useEffect, useState } from 'react';
import { Text, View, StyleSheet, ActivityIndicator, FlatList} from 'react-native';
export default function StateAndEffect() {
const [showData, setShowData] = useState(0);
useEffect(() => {
const timer=setTimeout(() => {
console.log("Before changing state...")
setShowData(showData+1)
console.log("After changing state...")
}, 1000);
// Clear timeout if the component is unmounted
return () => clearTimeout(timer);
});
console.log("I am rendering...")
return (
<View style={{flex:1, justifyContent:"center", alignItems:"center"}}>
<Text>
{console.log("Before updating render component...", showData)}
{showData}
{console.log("After updating render component...", showData)}
</Text>
</View>
)
};
If you see the log out put below you will see
I am rendering...
Before updating render component... 0
After updating render component... 0
Before changing state...
I am rendering...
Before updating render component... 1
After updating render component... 1
After changing state...
Before changing state...
I am rendering...
Before updating render component... 2
After updating render component... 2
After changing state...
Before changing state...
I am rendering...
Before updating render component... 3
After updating render component... 3
After changing state...
Before changing state...
I am rendering...
Before updating render component... 4
After updating render component... 4
After changing state...
Before changing state...
I am rendering...
Before updating render component... 5
After updating render component... 5
After changing state...
Before changing state...
I am rendering...
If you analyze the log carefully you will find, every time a state variable changes then render component happens first. There is a sudden change in the execution order of the code.
Because we passed the second argument as empty in useEffect hook, and at the same the we changed state value in useEffect hook, so it's get called infinitely and render keeps happening as you can see from the log.
So
√The hook useEffect hook second parameter is empty and change of state variable together causes infinitely call render.