An Introduction to Redux in React Native

Introduction

As a developer working with React Native, we often face difficulties in building complex applications, especially when it comes to passing data in nested components and managing states. This can be a time-consuming and challenging task. However, Redux offers a solution to this problem. In this article, we will explore how Redux can be used with React Native and discuss the following topics related to this subject.

  • What is Redux?
  • Why Prefer Redux
  • What Redux Offers
  • Redux Terminology
  • A Counter App In React Native With Redux
  • Conclusion

What is Redux?

Redux is a state management library that provides a centralized and predictable approach for React Native applications. Instead of scattering states across components, it stores the entire application state in a single, immutable object. The state updates are made through actions dispatched to a central reducer that ensures consistency and predictability. Additionally, Redux allows developers to time-travel and debug their applications efficiently.

Why Prefer Redux?

Passing data across components in React Native applications can be tricky, especially when dealing with sophisticated data flows and state management. Although props are the most common means of sending data from parent to child components, they can be difficult to use when components are deeply nested or dispersed around the application.

Why Prefer Redux

Let's look at the design above, where we want to send data from Component 1 to Component 6. Props are the standard technique for transmitting data from one component to another. However, constantly sending data through props and controlling the state can become difficult and stressful. To avoid headaches, it's critical to discover a more efficient solution.

In these cases, Redux can be used to store data in a centralized area, making it conveniently accessible whenever needed. The following are some of the advantages of adopting Redux:

The global state management system

Redux provides a centralized store for storing the application's whole state. This allows components to readily access and adjust any part of the state without the need for complicated props drilling.

An efficient development process

By eliminating the need for manual state management between components, using Redux for development provides for a more simplified workflow. As a result, the code is easier to maintain and organize.

What Redux Offers

What Redux Offers

Redux is a powerful state management library with a wide range of features. Here are some of the key features offered by Redux:

  • Predictable: This feature ensures that behaviour remains consistent and testing is simplified across various environments, including client, server, and native.
  • Centralized: Centralizing an application's state and logic provides powerful features such as undo/redo and state persistence.
  • Debuggable: Redux DevTools make it simple to trace changes in state, debug through time travel, and report errors.
  • Flexible: It adapts seamlessly to any user interface and provides a wide range of extensions for customizing the project.
  • Encapsulated: Components can seamlessly communicate with the Redux store using defined APIs, without the need for redundant code.
  • Optimized: By using advanced performance optimizations, this system ensures that user interfaces re-render only when required.

Redux Terminology

Redux Terminology

You can see how Redux works in the diagram above. Let's understand it with a real-life example.

Suppose a person named Krishna went to the market to buy some vegetables. After returning home, Krishna asked his wife, Radha, to store the vegetables in the refrigerator so they could be used as needed. Let's take a closer look at this simple scenario.

  • Action: Krishna's instruction to keep the vegetables in the refrigerator is like a Redux action. It's a simple command.
  • Reducer: Radha acts as the Redux reducer and puts vegetables in the refrigerator as instructed.
  • Store: The refrigerator is like the Redux store, holding vegetables for later use.

A Counter App In React Native With Redux

Let's begin by creating a basic counter application using Redux. Join me in following these steps to build it.

Step 1. Project Creation

The first step is to create a new project using the following command:

npx react-native init counterapp

Step 2. Package Installation

First, let's install the necessary packages. To install the packages, run the following command:

Using npm

#necessary packages for redux
npm install redux
npm install react-redux
npm install @reduxjs/toolkit

#installing these extra packages for native base and icon
npm install react-native-safe-area-context
nom install react-native-svg
npm install react-native-vector-icons
npm install native-base

Using yarn

#necessary packages for redux
yarn add redux
yarn add react-redux
yarn add @reduxjs/toolkit

#installing these extra packages for native base and icon
yarn add react-native-safe-area-context
yarn add react-native-svg
yarn add react-native-vector-icons
yarn add native-base

Step 3. Create Files and Folder Structure

In the following image, you can see how the files and project folders should be organized,

Redux Files and Folder Structure

Step 4. Create Constants

It's time to create action types constants.

//Location: src/utils/constants.js

export const ADD_NUMBER = 'add_number';
export const SUBSTRACT_NUMBER = 'substract_number';

Step 5. Create Action

Remember the story above, where Krishna told his wife to keep the vegetable in the refrigerator? Where Krishna's instruction is an action, In theory, we can say the same thing as,

A Redux action sends data from your application to the store. Normally, an action consists of two properties: its type and its payload (if it's included). The "type" refers to an uppercase string that is assigned to a constant and describes an action. The "payload" is extra data that can be included.

//Location: src/actions/counter_action.js
import { ADD_NUMBER, SUBSTRACT_NUMBER } from '../utils/constants';

export function incrementCount() {
    return {
        type: ADD_NUMBER,
    }
}

export function decrementCount() {
    return {
        type: SUBSTRACT_NUMBER,
    }
}

Step 6. Create Reducer

You may be wondering what "reducer" means. As we discussed earlier in the story, Radha (Krishna's wife) serves as the Redux reducer. She follows the instructions and puts the vegetables in the refrigerator. Technically speaking, this can be described as follows:

A reducer is a specialized function that has two parameters - state and action. It is immutable, meaning it cannot be modified, and always returns a duplicate of the whole state. Usually, a reducer comprises a switch statement that examines all possible action types.

//Location: src/reducers/counter_reducer.js
import { ADD_NUMBER, SUBSTRACT_NUMBER } from '../utils/constants';

export const counterReducer = (state = { count: 0 }, action) => {
    switch (action.type) {
        case ADD_NUMBER:
            return {
                ...state,
                count: state.count + 1
            }
        case SUBSTRACT_NUMBER:
            return {
                ...state,
                count: state.count - 1
            }
        default:
            return state;
    }
}

To proceed, it's time to create a root reducer. Your project may have multiple reducers, but the root reducer will consolidate all of them in one place.

Location: src/reducers/root_reducer.js
import {combineReducers} from 'redux';
import { counterReducer } from './counter_reducer';

const rootReducer = combineReducers({
    counterReducer
})

export default rootReducer;

Step 7. Create Store

In the story above, we saw how the refrigerator served as a central hub for storing vegetables. From a technical perspective, we can summarize it as follows:

The Redux application state resides in the store, which is initialized with a reducer.

Location: src/store.js
import {configureStore} from '@reduxjs/toolkit';
import rootReducer from './reducers/root_reducer';

const store = configureStore({
    reducer: rootReducer //pass the root reducer in the reducer parm
})

export default store;

Step 8. Create View (For Functional Component)

Before creating a counter component, it's important to first understand the two hooks of React Redux.

useDispatch()

The useDispatch hook in React Redux allows components to easily trigger actions and update the Redux store without the need for complex setup or passing functions as props. This simplifies the process of dispatching actions in functional components.

useSelector()

The useSelector hook in React Redux is utilized to extract and access data from the Redux store in functional components. This simplifies the process of managing state access and subscriptions in React applications. With this hook, components can easily choose and subscribe to specific parts of the state.

//Location: src/components/Counter.js
import { Text, StyleSheet, View } from 'react-native';
import { Fab } from 'native-base';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { incrementCount, decrementCount } from '../actions/counter_action';
import { useDispatch, useSelector } from 'react-redux';

export default function Counter() {

    const dispactch = useDispatch();
    const count = useSelector((state) => state.counterReducer.count);

    const increment = () => {
        dispactch(incrementCount());
    }

    const decrement = () => {
        dispactch(decrementCount());
    }

    return (
        <View style={styles.mainContainer}>
            <View style={styles.counterMainContainer}>
                <View style={styles.counterContainer}>
                    <Text style={styles.textStyle}>{count}</Text>
                </View>
            </View>

            <View>
                <Fab
                    placement="bottom-right"
                    colorScheme="blue"
                    size="lg"
                    icon={<MaterialCommunityIcons name="minus" size={26} color="white" />}
                    onPress={decrement}
                />
                <Fab
                    placement="bottom-left"
                    colorScheme="blue"
                    size="lg"
                    icon={<MaterialCommunityIcons name="plus" size={26} color="white" />}
                    onPress={increment}
                />

            </View>
        </View>
    )
}

const styles = StyleSheet.create({
    mainContainer: {
        alignSelf: 'center'
    },

    counterMainContainer: {
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center'
    },
    counterContainer: {
        justifyContent: 'center',
        alignItems: 'center',
        width: 200,
        height: 200,
        backgroundColor: 'white',
        borderRadius: 100,
    },
    textStyle: {
        fontSize: 40,
        color: 'black',
        fontWeight: 'bold'
    }
})

Step 9. Create View (For Class Component)

If you are comfortable working with class components, you can skip step 8. There are additional hooks available for class components in React Redux. Before we go any further, let's talk about those.

mapStateToProps()

In React Redux, the mapStateToProps hook connects a component to the Redux store. This enables the component to easily access and update specified bits of the state via the “useSelector” hook. It improves process efficiency by facilitating data retrieval and maintaining the component in sync with the store.

mapDispatchToProps()

In React Redux, the hook mapDispatchToProps connects a component to the Redux store. Using the “useDispatch” hook, the component may effortlessly dispatch actions and update the state.

connect() 

The connect hook is used to link a component to the Redux store. This is accomplished by providing mapStateToProps and/or mapDispatchToProps functions, which allow the component to efficiently retrieve the state and dispatch operations. This simplifies interaction with the Redux store and ensures that the component remains in sync with the state.

To learn more about these hooks, go to https://react-redux.js.org/api/connect

//Location: src/components/Counter.js
import { Text, StyleSheet, View } from 'react-native';
import React, { Component } from 'react';
import { Fab } from 'native-base';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { incrementCount, decrementCount } from '../actions/counter_action';
import { connect } from 'react-redux';

function mapStateToProps(state) {
    return {
        count: state.counterReducer.count
    }
}

function mapDispatchToProps(dispatch) {
    return {
        increment: () => dispatch(incrementCount()),
        decrement: () => dispatch(decrementCount()),
    };
}

class Counter extends Component {

    constructor(props) {
        super(props);
    }

    render() {
        return (
            <View style={styles.mainContainer}>
                <View style={styles.counterMainContainer}>
                    <View style={styles.counterContainer}>
                        <Text style={styles.textStyle}>{this.props.count}</Text>
                    </View>
                </View>

                <View>
                    <Fab
                        placement="bottom-right"
                        colorScheme="blue"
                        size="lg"
                        icon={<MaterialCommunityIcons name="minus" size={26} color="white" />}
                        onPress={this.props.decrement}
                    />
                    <Fab
                        placement="bottom-left"
                        colorScheme="blue"
                        size="lg"
                        icon={<MaterialCommunityIcons name="plus" size={26} color="white" />}
                        onPress={this.props.increment}
                    />

                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    mainContainer: {
        alignSelf: 'center'
    },

    counterMainContainer: {
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center'
    },
    counterContainer: {
        justifyContent: 'center',
        alignItems: 'center',
        width: 200,
        height: 200,
        backgroundColor: 'white',
        borderRadius: 100,

    },
    textStyle: {
        fontSize: 40,
        color: 'black',
        fontWeight: 'bold'
    }
})

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Step 10. Create Redux Wrapper

We are almost done. We just need to wrap the react native main component around the redux provider. To do this, we need to create a React Redux provider where we can pass the store and keep the main app component inside the provider.

//Location: index.js
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import {Provider} from 'react-redux';
import store from './src/store';

const AppRedux = () => (
    <Provider store={store}>
        <App />
    </Provider>
)

AppRegistry.registerComponent(appName, () => AppRedux);

Step 11. Wrap App Component

For this project, I am using the Native Base library, which requires us to wrap the main App component inside the NativeBaseProvider. If you are not using the native base library for your project then you can skip this step.

//Location: App.js
import React, { Component } from 'react';
import { View } from 'react-native';
import Counter from './src/components/Counter';
import { NativeBaseProvider } from 'native-base';

export default class App extends Component {
  render() {
    return (
      <NativeBaseProvider>
        <View>
          <Counter />
        </View>
      </NativeBaseProvider>

    )
  }
}

You're all set! Go ahead and run your app to start playing with it.

Counter App In React Native With Redux

The source code can be downloaded from the GitHub link provided below.

Conclusion

In summary, Redux is a valuable JavaScript library for React Native developers who want to efficiently manage state and data flow in complex applications. It enhances code organization, improves performance, and provides an overall better development experience. With Redux, it's easy to access and modify the global state, which leads to the creation of scalable, maintainable, and debuggable applications. However, it's worth noting that not every project needs Redux. Depending on the project's complexity and use case, developers can make an informed decision on whether to use it or not. Thank you for taking the time to read this. I hope you enjoyed this article! You can connect with me on LinkedIn to say hi.

Next article >> Getting Started with Redux Saga in React Native


Similar Articles