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)