DEV Community

Cover image for Using React Hooks in React Native: Best Practices
Saurabh Dhariwal
Saurabh Dhariwal

Posted on

Using React Hooks in React Native: Best Practices

What Are React Hooks?

React Hooks was introduced in React 16.8 which gives a new feature called state and lifecycle methods in functional components. It makes it possible for developers to use state and other React features without necessarily converting functional components into class components. The most frequently used are the following Hooks:

useState: To handle state inside a functional component.
useEffect: To handle side effects, such as data fetching or subscriptions.
useContext: To access context values without prop drilling.-
useReducer: To manage more complex state in functional components.

With these hooks, you can write functional components that are more powerful and flexible, enabling a comprehensive and efficient approach to React Native development.

1. Getting Started with Basic Hooks

Before we dive into best practices, let's briefly understand how to use the basic hooks effectively:

useState :
This hook is where you could attach state to functional components. Below is an easy example.

import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';

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

const increment = () => {
setCount(count + 1);
};

return (
<View>
<Text>Count: {count}</Text>
<Button title="Increment" onPress={increment} />
</View>
);
};
Enter fullscreen mode Exit fullscreen mode

useEffect:
This hook allows you to perform side effects on your components. A common use case is data fetching:

import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';

const DataFetcher = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
setLoading(false);
};

fetchData();
}, []); // Empty dependency array: run once on mount

if (loading) {
return <Text>Loading.</Text>;
}

return <Text>Data: {JSON.stringify(data)}</Text>;
};
Enter fullscreen mode Exit fullscreen mode

 2. Organizing Your Component Logic with Hooks

Organizing logic is one of the huge benefits of using hooks- this can be done cleaner compared to class components. Some good practices are listed  below:

Group Related Logic

Maintain Logical Grouping: Whenever there are related state(s) or side effects, maintain their grouping together within the same component or custom hook. This has better readability and maintainability.

Usage:

const Form = () => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = () => {
setIsSubmitting(true);
// form submit logic here.
`` End.
};

return (
<View>
<TextInput value={name} onChangeText={setName} placeholder="Name" />
<TextInput value={email} onChangeText={setEmail} placeholder="Email" />
<Button title="Submit" onPress={handleSubmit} disabled={isSubmitting} />
</View>
);
};
Enter fullscreen mode Exit fullscreen mode

Create Custom Hooks

Reuse Logic: If you find yourself repeating logic across components, consider creating custom hooks. This promotes DRY (Don't Repeat Yourself) principles and keeps your components clean.

Example


import { useState, useEffect } from 'react';

const useFetch = (url) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};

fetchData();
}, [url]);

return { data, error, loading };
};
Enter fullscreen mode Exit fullscreen mode

You can then use this hook in any component:

const App = () => {
const { data, error, loading } = useFetch('https://api.example.com/data');

if (loading) return <Text>Loading.</Text>;
if (error) return <Text>Error: {error.message}</Text>;

return <Text>Data: {JSON.stringify(data)}</Text>;
};
Enter fullscreen mode Exit fullscreen mode

3. Controlling Side Effects Properly

In React Native development, it is important to control side effects, especially when using APIs or subscriptions. Here's how to control side effects properly:

 Careful Use of Dependency Array

The dependency array in useEffect dictates when the effect should re-run. Use it carefully to prevent unnecessary re-renders.

Example

useEffect(() => {
const timer = setTimeout(() =>
console.log('Effect executed!');
}, 1000);

return () => clearTimeout(timer); // Cleanup function
}, [someDependency]); // Effect runs when 'someDependency' changes
Enter fullscreen mode Exit fullscreen mode

Cleanup Effects

Always return a cleanup function if your effect does something that needs to be cleaned up, such as subscription or timers, to avoid memory leaks.

Example

useEffect(() => {
const subscription = someAPI.subscribe();

return () => {
subscription.unsubscribe(); // Cleanup on unmount
};
}, []);
Enter fullscreen mode Exit fullscreen mode

4. Performance Optimization with Hooks

Performance in React Native development happens with efficient use of hooks. Here are some strategies that would help in optimizing the performance:

Memoization using useMemo and useCallback

Avoid unnecessary renders by using useMemo for expensive computation, and useCallback for functions passed as props to prevent unnecessary renders.

Here's an example with useMemo,

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Enter fullscreen mode Exit fullscreen mode

Here's an example with useCallback

const handleClick = useCallback(() => {
console.log('Clicked!');
}, []);
Enter fullscreen mode Exit fullscreen mode

Both hooks enable React to determine changes in values or functions more accurately, hence reducing the number of renders.

Batch State Updates

React automatically batches state updates inside event handlers, but you can also use it elsewhere to improve performance.

Example

const handleClick = () => {
setCount(count + 1);
setAnotherState(anotherValue);
}; // React batches these updates together
Enter fullscreen mode Exit fullscreen mode

5. Error Handling in React Hooks

Error handling is one of the most significant concerns when dealing with asynchronous operations in React Native development. Here's how to handle errors effectively:

 Error States

Always implement error states and render them conditionally within your components. This enhances the user experience through clear feedback.

Example

const DataFetcher = () => {
const { data, error, loading } = useFetch('https://api.example.com/data');

if (loading) return <Text>Loading.</Text>;
if (error) return <Text>Error: {error.message}</Text>;

return <Text>Data: {JSON.stringify(data)}</Text>;
};
Enter fullscreen mode Exit fullscreen mode

6. Using React Context with Hooks

React Context is the best way to pass data through the component tree without having to pass props down manually at every level. Along with hooks, it improves your app's architecture.

Configuring Context

Create Context:

import { createContext } from 'react';

const ThemeContext = createContext();
Enter fullscreen mode Exit fullscreen mode

Provide Context:

const App = () => {
const theme = { primaryColor: '#007AFF' };

return (
<ThemeContext.Provider value={theme}>
<SomeComponent />
</ThemeContext.Provider>
);
};
Enter fullscreen mode Exit fullscreen mode

Consume Context with useContext:

import { useContext } from 'react';

const SomeComponent = () => {
const theme = useContext(ThemeContext);

return <Text style={{ color: theme.primaryColor }}>Hello World!</Text>;
};
Enter fullscreen mode Exit fullscreen mode

7. Managing Complex State with useReducer

When state management becomes complex, especially in larger applications, use useReducer. This hook makes the management of state transitions in functional components more effective.

Example

const initialState = { count: 0 };

const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
};

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

return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
};
Enter fullscreen mode Exit fullscreen mode

Using useReducer provides a more structured approach to managing complex state and can even make your code easier to test.

8. Testing Components with Hooks

Testing components that use hooks is crucial for ensuring that your application behaves as expected. Tools like React Testing Library make this straightforward.

Example:

This is how you might test a component that uses hooks:

import { render, fireEvent } from '@testing-library/react-native';
import Counter from './Counter';

test('increments counter', () => {
const { getByText } = render(<Counter />);
//
fireEvent.press(getByText('+'));
expect(getByText('Count: 1')).toBeTruthy();
});
Enter fullscreen mode Exit fullscreen mode

9. Common Pitfalls to Avoid

While hooks offer powerful capabilities, here are some common pitfalls to avoid:

- Avoid using hooks inside loops, conditions, or nested functions: Hooks have to be called in the same order every time the render is called. Always call hooks at the top level of your React functions.

- Handle stale closures: Use the state values in callbacks. It ensures that the latest values are captured, not stale data:


javascript
const handleClick = () => {
setTimeout(() => {
console.log(state); // This may log an out of date state
}, 1000);
};

-
- **Use the functional form of `setState` when depending on the previous state**:

setCount(prevCount => prevCount + 1);

### 10. Conclusion

React Hooks is a very powerful toolset for managing state and side effects in React Native development. With the best practices covered in this blog, you will be able to write cleaner, more maintainable, and more efficient code. The flexibility and organizational capabilities of hooks can make your development workflow much better.

As you grow [developing React Native applications](https://www.addwebsolution.com/our-capabilities/react-native-app-development), continue learning and finding more about the ecosystem of hooks, deepen your knowledge on state management, and hone error handling and performance optimization best practices. Leverage the capabilities of React Hooks, and your React Native development will know no bounds.

Enter fullscreen mode Exit fullscreen mode

Top comments (0)