Search

Suggested keywords:
  • Java
  • Docker
  • Git
  • React
  • NextJs
  • Spring boot
  • Laravel

Mastering Hooks in React Native: A Deep Dive

  • Share this:

post-title

React Native Hooks are functions that empower functional components to utilize state, lifecycle methods, and more. Introduced in React 16.8, they offer a concise way to handle logic in functional components.

To learn basics of React Native and Expo, refer to our react native series post.

Benefits of Hooks

Reuse Logic:

Hooks enable the reuse of stateful logic without changing the component hierarchy.

Example:

 const useCustomLogic = () => {
    const [value, setValue] = useState(0);

    const increment = () => {
      setValue(value + 1);
    };

    return [value, increment];
  };

  // Usage
  const [count, incrementCount] = useCustomLogic();

 

Better Code Organization: Code that uses hooks tends to be more readable and modular.

No Need for Class Components: Hooks eliminate the need for class components for state management.

 

Basic Rules of Hooks

1. Use in Functional Components: Hooks can only be used in functional components or custom hooks.

2. Top-Level Calls: Hooks should be called at the top level of the component.

3. Consistent Order: Hooks must be called in the same order in every render.

 

useState Hook: Managing State in Functional Components

The useState hook allows functional components to have local state. It returns an array with two elements: the current state value and a function that lets you update it.

Practical Examples and Use Cases:

- Managing form input values.

const [inputValue, setInputValue] = useState('');

 

- Toggling visibility or enabling/disabling features.

  const [isVisible, setIsVisible] = useState(true);

 

- Creating counters and dynamic content.

  const [count, setCount] = useState(0);

 

useEffect Hook: Side Effects in Functional Components

The useEffect hook is used for handling side effects in functional components. Side effects include data fetching, subscriptions, or manually changing the DOM.

Practical Examples and Use Cases:

- Fetching data from an API.

  useEffect(() => {
   fetchData();
 }, []);

 

- Subscribing to external events.

  useEffect(() => {
   const subscription = subscribeToEvent();
   return () => subscription.unsubscribe();
 }, []);

 

- Manually changing the DOM or performing cleanup.

  useEffect(() => {
   document.title = 'New Page Title';
   return () => {
     // Cleanup logic here
   };
 }, []);

 

useContext Hook: Simplifying State Management with Context

The useContext hook provides a simple way to access values stored in the context. Context allows you to pass data through the component tree without having to pass props manually.

Practical Examples and Use Cases:

- Managing global application state.

  const theme = useContext(ThemeContext);

 

- Handling user authentication across components.

  const user = useContext(UserContext);

 

useReducer Hook: Centralizing Complex State Logic

The useReducer hook is useful for managing complex state logic in functional components. It is often preferable to useState when the next state depends on the previous one.

Practical Examples and Use Cases:

- Managing state transitions in a finite state machine.

  const [state, dispatch] = useReducer(reducer, initialState);

 

- Handling complex form validations and state changes.

 const [formState, dispatchFormAction] = useReducer(formReducer, initialFormState);

 

useCallback and useMemo Hooks: Optimizing Performance with Memoization

The useCallback hook is used to memoize functions, while the useMemo hook is used to memoize values. Memoization optimizes performance by preventing unnecessary re-renders.

Practical Examples and Use Cases:

- Memoizing event handlers.

  const handleClick = useCallback(() => {
   // Handle click logic
 }, [dependency]);

- Memoizing the result of a computation to avoid unnecessary recalculations.

  const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

 

useRef Hook: Accessing and Interacting with DOM Elements

The useRef hook provides a way to access and interact with a DOM element directly. It is commonly used to persist values across renders without causing re-renders.

Practical Examples and Use Cases:

- Referencing and interacting with DOM elements directly.

  const myRef = useRef();

- Storing mutable values that persist across renders without causing re-renders.

const mutableValue = useRef(initialValue);

Now, let’s build a Demo NoteBook app to deeply understand what we’ve learnt and use it to make something of practical usage.

 

App Setup

Installation

Ensure you have Node.js and npm installed.

Run the following commands to set up a new React Native project using Expo:

npm install -g expo-cli
expo init NotebookApp
cd NotebookApp

 

Folder Structure

/NotebookApp
|-- /src
|   |-- /components
|       |-- NoteContext.js
|       |-- NoteReducer.js
|       |-- NoteList.js
|       |-- AddNoteForm.js
|   |-- App.js
|-- /assets
|   |-- /images
|       |-- note.png
|-- /node_modules
|-- package.json
|-- App.js
|-- .gitignore
|-- README.md
|-- ... (other project files)

 

NoteContext.js

import React, { createContext, useReducer, useContext } from 'react';
import noteReducer from './NoteReducer';

const NoteContext = createContext();

export const useNoteContext = () => {
 const context = useContext(NoteContext);
 if (!context) {
   throw new Error('useNoteContext must be used within a NoteProvider');
 }
 return context;
};

export const NoteProvider = ({ children }) => {
 const [notes, dispatch] = useReducer(noteReducer, []);

 const addNote = (note) => {
   dispatch({ type: 'ADD_NOTE', payload: note });
 };

 const removeNote = (noteId) => {
   dispatch({ type: 'REMOVE_NOTE', payload: noteId });
 };

 return (
   <NoteContext.Provider value={{ notes, addNote, removeNote }}>
     {children}
   </NoteContext.Provider>
 );
};

This file is responsible for managing the context related to notes using the useReducer hook. It provides a NoteProvider component, which wraps the entire application, and a custom hook useNoteContex to access the state and actions related to notes.

Purpose:

  - Create a context for managing notes.

  - Provide a custom hook to access the context in functional components.

  - Utilize the useReducer hook to manage state and actions.

 

NoteReducer.js

const noteReducer = (state, action) => {
 switch (action.type) {
   case 'ADD_NOTE':
     return [...state, { id: Date.now(), text: action.payload }];
   case 'REMOVE_NOTE':
     return state.filter((note) => note.id !== action.payload);
   default:
     return state;
 }
};

export default noteReducer;

This file contains the reducer function used by useReducer in `NoteContext.js`. The reducer handles actions such as adding and removing notes, updating the state accordingly.

Purpose:

  - Define the reducer function to manage state transitions based on actions.

  - Handle actions like adding a new note and removing an existing note.

 

NoteList.js

import React from 'react';
import { FlatList, View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useNoteContext } from './NoteContext';

const NoteList = () => {
 const { notes, removeNote } = useNoteContext();

 const renderItem = ({ item }) => (
   <View style={styles.noteItem}>
     <Text style={styles.noteText}>{item.text}</Text>
     <TouchableOpacity onPress={() => removeNote(item.id)}>
       <Text style={styles.removeButton}>Remove</Text>
     </TouchableOpacity>
   </View>
 );

 return (
   <FlatList
     data={notes}
     keyExtractor={(item) => item.id.toString()}
     renderItem={renderItem}
   />
 );
};

const styles = StyleSheet.create({
 noteItem: {
   flexDirection: 'row',
   justifyContent: 'space-between',
   alignItems: 'center',
   padding: 10,
   borderBottomWidth: 1,
   borderBottomColor: '#ccc',
 },
 noteText: {
   fontSize: 16,
 },
 removeButton: {
   color: 'red',
   fontSize: 16,
 },
});

export default NoteList;

This component renders a list of notes using the FlatList component. It subscribes to the note context using the useNoteContext hook and displays each note with an option to remove it.

Purpose:

  - Display a list of notes fetched from the context.

  - Provide a visual representation of each note with a remove option.

 

AddNoteForm.js

import React, { useState } from 'react';
import { View, TextInput, Button, StyleSheet } from 'react-native';
import { useNoteContext } from './NoteContext';

const AddNoteForm = () => {
 const [noteText, setNoteText] = useState('');
 const { addNote } = useNoteContext();

 const handleAddNote = () => {
   if (noteText.trim() !== '') {
     addNote(noteText);
     setNoteText('');
   }
 };

 return (
   <View style={styles.addNoteForm}>
     <TextInput
       style={styles.input}
       placeholder="Enter note text"
       value={noteText}
       onChangeText={setNoteText}
     />
     <Button title="Add Note" onPress={handleAddNote} />
   </View>
 );
};

const styles = StyleSheet.create({
 addNoteForm: {
   padding: 10,
   borderBottomWidth: 1,
   borderBottomColor: '#ccc',
 },
 input: {
   height: 40,
   borderColor: 'gray',
   borderWidth: 1,
   marginBottom: 10,
   paddingLeft: 8,
 },
});

export default AddNoteForm;

This component represents the form for adding new notes. It uses the useState hook to manage the input field's state and the useNoteContext hook to dispatch the ADD_NOTE action.

Purpose:

  - Render a form for users to input new notes.

  - Use state to manage the input field.

  - Dispatch the `ADD_NOTE` action when the user adds a new note.

 

App.js

import React from 'react';
import { SafeAreaView, StyleSheet } from 'react-native';
import { NoteProvider } from './src/components/NoteContext';
import NoteList from './src/components/NoteList';
import AddNoteForm from './src/components/AddNoteForm';

const App = () => {
 return (
   <NoteProvider>
     <SafeAreaView style={styles.container}>
       <AddNoteForm />
       <NoteList />
     </SafeAreaView>
   </NoteProvider>
 );
};

const styles = StyleSheet.create({
 container: {
   flex: 1,
   backgroundColor: '#fff',
 },
});

export default App;

The main entry point of the application. It wraps the entire application with the NoteProvider to ensure that the note context is available to all components. It includes the AddNoteForm and NoteList components.

Purpose:

  - Render the entire application.

  - Wrap the app with the `NoteProvider` to provide the note context.

  - Include the `AddNoteForm` and `NoteList` components.

 

Demo App

screenshot

 

Adding a Note

screenshot

 

Removing a Note

screenshot

 

Conclusion

Our exploration into React Native hooks has unveiled a powerful arsenal for crafting dynamic and efficient mobile applications. From the foundational `useState` and `useEffect` hooks to the nuanced capabilities of `useContext`, `useReducer`, and others, we've navigated through the theoretical landscape.

By applying these concepts in our unified "Notebook" application, we've witnessed firsthand how React Native hooks simplify state management, streamline logic, and elevate the overall development experience. The clear folder structure and modular components have not only demonstrated the practical implementation of these hooks but also emphasized the importance of clean and organized code.

As you embark on your React Native journey, armed with this newfound understanding of hooks, may your projects flourish with enhanced functionality and maintainability. Experiment, iterate, and build with confidence, knowing that React Native hooks are your steadfast companions in the realm of mobile app development.

Happy coding!

Utkarsh Krishna

About author
Hey there! I'm Utkarsh Krishna, an Open Source, Full-Stack Web Development, Java Development, and Developer Blogging enthusiast. Currently, I'm learning Full-Stack Web Development and Java, and I'm excited to share my journey and learnings through my blogs. Join me on this exciting adventure as we explore the world of software development together!