React Native hooks provide an intuitive way to manage state and side effects in your components. Introduced in React 16.8, hooks allow us to use state and other React features without writing class components, making functional components powerful and more readable.
In this article, we will explore the most commonly used hooks, how they work, and how you can leverage them in your React Native projects. We’ll also look at how to create custom hooks.
Why Hooks?
Hooks are functions that let you “hook into” React state and lifecycle features from function components. They make your code cleaner, easier to understand, and reusable.
useState
useState
is one of the most commonly used hooks. It lets you add state to functional components. With useState
, you can create variables that store component-level data and re-render your component whenever that data changes.
import React, { useState } from 'react';
import { Text, Button, View } from 'react-native';
const Counter = () => {
const [count, setCount] = useState<number>(0);
return (
<View>
<Text>Count: {count}</Text>
<Button title="Increase" onPress={() => setCount(count + 1)} />
</View>
);
};
export default Counter;
How it works:
useState
takes the initial value (0 in the example) and returns an array containing the current value (count
) and a function to update it (setCount
).
useEffect
useEffect
is used to perform side effects in function components. It runs after every render by default but can also be customized to run only when specific values change.
import React, { useState, useEffect } from 'react';
import { Text, View } from 'react-native';
const DataFetcher = () => {
const [data, setData] = useState<string | null>(null);
useEffect(() => {
// Simulate fetching data from an API
const fetchData = async () => {
const result = await new Promise((resolve) =>
setTimeout(() => resolve("Fetched Data"), 2000)
);
setData(result as string);
};
fetchData();
}, []); // empty array ensures this runs once when the component mounts
return <View>{data ? <Text>{data}</Text> : <Text>Loading...</Text>}</View>;
};
export default DataFetcher;
How it works:
useEffect
runs after the component renders. In this case, we simulate fetching data, and the effect only runs once because of the empty dependency array []
.
useContext
useContext
allows you to use values from React's Context API. This is helpful for passing data like theme or authentication information without prop drilling.
import React, { createContext, useContext } from 'react';
import { Text, View } from 'react-native';
// Create a context
const ThemeContext = createContext('light');
const ThemedComponent = () => {
const theme = useContext(ThemeContext);
return <Text>Current Theme: {theme}</Text>;
};
const App = () => (
<ThemeContext.Provider value="dark">
<View>
<ThemedComponent />
</View>
</ThemeContext.Provider>
);
export default App;
How it works:
useContext
allows you to consume the value of the nearest ThemeContext.Provider
, eliminating the need to pass down props manually.
useReducer
useReducer
is similar to useState
but is used for more complex state logic. It is often used when the state has multiple transitions.
import React, { useReducer } from 'react';
import { Text, Button, View } from 'react-native';
type Action = { type: 'increment' | 'decrement' };
const reducer = (state: number, action: Action) => {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
};
const Counter = () => {
const [count, dispatch] = useReducer(reducer, 0);
return (
<View>
<Text>Count: {count}</Text>
<Button title="Increase" onPress={() => dispatch({ type: 'increment' })} />
<Button title="Decrease" onPress={() => dispatch({ type: 'decrement' })} />
</View>
);
};
export default Counter;
How it works:
useReducer
takes a reducer function and an initial state, returning the current state and a dispatch
function to trigger state changes.
useCallback
useCallback
is used to memoize functions so they aren’t recreated on every render, which can be useful for optimization in child components.
import React, { useState, useCallback } from 'react';
import { Text, Button, View } from 'react-native';
const ChildComponent = React.memo(({ onClick }: { onClick: () => void }) => (
<Button title="Click me" onPress={onClick} />
));
const ParentComponent = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []); // Function is memoized, won't recreate unless dependencies change
return (
<View>
<Text>Count: {count}</Text>
<ChildComponent onClick={increment} />
</View>
);
};
export default ParentComponent;
How it works:
useCallback
returns a memoized version of the increment
function, ensuring it is not recreated unless dependencies change.
useMemo
useMemo
is used to memoize values instead of functions. It recalculates the value only when dependencies change, improving performance in expensive calculations.
import React, { useMemo, useState } from 'react';
import { Text, Button, View } from 'react-native';
const ExpensiveComponent = () => {
const [count, setCount] = useState(0);
const expensiveCalculation = useMemo(() => {
// Simulate an expensive computation
return count * 1000;
}, [count]);
return (
<View>
<Text>Result: {expensiveCalculation}</Text>
<Button title="Increase Count" onPress={() => setCount(count + 1)} />
</View>
);
};
export default ExpensiveComponent;
How it works:
useMemo
ensures the expensive calculation runs only when count
changes.
useRef
useRef
is used to persist values across renders without causing a re-render. It’s often used to reference DOM elements or hold mutable values.
import React, { useRef } from 'react';
import { Button, TextInput, View } from 'react-native';
const InputFocus = () => {
const inputRef = useRef<TextInput | null>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<View>
<TextInput ref={inputRef} style={{ height: 40, borderColor: 'gray', borderWidth: 1 }} />
<Button title="Focus Input" onPress={focusInput} />
</View>
);
};
export default InputFocus;
How it works:
useRef
creates a reference to the TextInput
component, allowing us to programmatically control its focus.
useLayoutEffect
useLayoutEffect
works similarly to useEffect
but is fired synchronously after all DOM mutations, ensuring your effect runs before the browser paints.
import React, { useLayoutEffect, useState } from 'react';
import { Text, View } from 'react-native';
const LayoutEffectComponent = () => {
const [layout, setLayout] = useState({ width: 0, height: 0 });
useLayoutEffect(() => {
// This effect runs before the browser updates the screen
setLayout({ width: 100, height: 100 });
}, []);
return (
<View style={{ width: layout.width, height: layout.height }}>
<Text>Layout size: {layout.width} x {layout.height}</Text>
</View>
);
};
export default LayoutEffectComponent;
How it works:
useLayoutEffect
is useful for reading layout measurements and synchronizing DOM mutations.
Creating Custom Hooks
Custom hooks allow you to extract and reuse logic from components. They are simply functions that use hooks internally.
import { useState, useEffect } from 'react';
const useFetchData = (url: string) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(url);
const result = await response.json();
setData(result);
setLoading(false);
};
fetchData();
}, [url]);
return { data, loading };
};
export default useFetchData;
You can now use useFetchData
in any component to fetch data.
import React from 'react';
import { Text, View } from 'react-native';
import useFetchData from './useFetchData';
const DataComponent = () => {
const { data, loading } = useFetchData('https://jsonplaceholder.typicode.com/todos/1');
return (
<View>{loading ? <Text>Loading...</Text> : <Text>{JSON.stringify(data)}</Text>}</View>
);
};
export default DataComponent;
React hooks, combined with TypeScript, offer a powerful and concise way to manage state and side effects in your React Native apps. By mastering hooks like useState
, useEffect
, and others, you can write cleaner, more efficient components. Additionally, creating custom hooks allows you to reuse logic across your application.
That's it for today. In the next part of this series, we’ll explore advanced topics like navigation, animations, and working with APIs.
Feel free to reach out to me if you have any questions or need assistance.
LinkedIn: https://www.linkedin.com/in/rushikesh-pandit-646834100/
GitHub: https://github.com/rushikeshpandit
Portfolio: https://www.rushikeshpandit.in
#ReactNative #TypeScript #MobileDevelopment #SoftwareEngineering #DevCommunity
Top comments (0)