Redux vs Recoil: Choosing the right state management library for React Native

State administration is one of the most important skills that every front-end or mobile application developer should master. Not only does it make the application more fluid or efficient, but it also makes the code easier to maintain when good tools are used. In the React Native ecosystem, you can find Reduxwhich is the most popular library used for state administration, even before React’s Contextual API. Redux is followed closely by Repulse, which (unlike Redux) is very easy to install. Which of these two state management solutions should you choose for your next project?

In this article, we will compare these two state management solutions using both of them to build a simple counter application with React Native.

Screenshot from 2022-03-01 15-06-23.png#center

Redux vs Recoil: Architecture

Redux architecture

The Redux architecture relies on one rule: All your application states live in one container. To change the status, you will need to create a new status based on the current status and requested change. This is why Redux has these three main components:

  • store that holds all the state of your app
  • Actionwhich is immutable data that describes a state change
  • Reducer this changes the state of the program using the current state and action

Redux comes with some problems, however, including the following:

  • Steep learning curve
  • Too much code
  • Involves redesigning your project
  • Lack of concurrent mode support
  • Generic approach is not React-like
  • Difficult to achieve code sharing
  • No built-in async support

소액결제 현금화 방법

Repulse is a brand new experimental JavaScript state management library that addresses many of the problems developers face when developing large applications using the existing Context API. Recopy has two main components – atoms and selectors.

Atoms are units of the state. They are also upgradeable and subscribeable. This means that when an atom is updated, each subscribed component is represented with the new value.

Selectors are pure functions that accept atoms or other selectors as arguments. When these downstream atoms or selectors are updated, the selection function is reevaluated.

Components can subscribe to selectors just as they can to atoms. They are then represented when a selector changes. Selectors can transform the atomic state either synchronously or asynchronously.

Redux vs Recoil: Battle

To really understand the difference between Redux and Recoil, let’s quickly create a simple React Native application that increments a counter with the click of a button.

Setting up the project

First of all, you will need to set up a project. We will make a mobile app using React Native. So, make sure you have your environment ready for React Native. In this example, I’m working on a Linux machine.

npx react-native init ReduxVsRecoil

Once that is done, we can add Redux and create the first version of the counter application.

Adding Redux

In this step, we will add Redux packages.

yarn add redux react-redux

Redux is a standalone library, and react-redux gives us access to some hooks to make development with React easier.

Adding reducers

As said before, in Redux, actions are objects that describe what needs to be done.

On the other hand, Redux reducers are simple functions that check which action is performed and update the state according to the action.

To make this clearer, let’s create a folder called store in the src directory. This folder will contain all the actions and reducers that we will write. In this folder, let’s create a reducer — a file called counterReducer.js.

const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
};

export default counterReducer;

For this reducer, the state is hardcoded to 0. Next, we need to register the reducer. For that, Redux provides the helper function combineReducers. This function will transform all reducers into a single reducer that can be passed to the createStore API. We will talk more about this API in the following sections. Currently, within the store directory, let’s create a file called reducers.js and implement the combineReducers operate there.

import {combineReducers} from 'redux';
import counter from './counterReducer';

const allReducers = combineReducers({
  counter,
});

export default allReducers;

Adding actions

Actions in Redux are JSON objects that describe what needs to be done. Let’s add the action that will increase our value. To begin, create a file named actions.js in the store folder.

export const increment = () => {
  return {
    type: 'INCREMENT',
  };
};

export const decrement = () => {
  return {
    type: 'DECREMENT',
  };
};

After that, we need to create the store that the React Native application will use to update the state. Within the src/store directory, let’s create a file called index.js.

import {createStore} from 'redux';
import allReducers from './reducers';

const store = createStore(allReducers);

export default store;

The Redux store is created with the createStore API. We can now register the store within our React Native application.

Wrapping the React Native project

Before we start importing and sending actions, we need to create a global store. To bring the functionality of our counter app to life, we need to connect everything we’ve done with Redux to the app.

We have to import Provider of react-redux and wrap it around the whole app in our src/index.js file. The provider connects the global state to the app. Provider takes an argument called store in which we have to pass the created store.

/**
 * @format
 */
import React from 'react';
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import {Provider} from 'react-redux';
import store from './store';

const ReduxApp = () => {
  return (
    <Provider store={store}>
      <App />
    </Provider>
  );
};

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

React-Redux comes with two hooks that can speed up state selection and performance: useDispatch and useSelector.

Let’s rewrite the App.js file to create the counter and start incrementing and decrementing it.

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React from 'react';
import {SafeAreaView, StyleSheet, Text, View, Button} from 'react-native';
import {useSelector, useDispatch} from 'react-redux';

const App = () => {
  const {counter} = useSelector(state => state);

  const dispatch = useDispatch();

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.counterContainer}>
        <Text style={styles.counterText}>{counter}</Text>
      </View>
      <View style={styles.buttonContainer}>
        <Button
          title="Increment"
          onPress={() => dispatch({type: 'INCREMENT'})}
          color="#5000ca"
        />
        <Button
          title="Decrement"
          onPress={() => dispatch({type: 'DECREMENT'})}
          color="#5000ca"
        />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  counterContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonContainer: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    width: '70%',
  },
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  counterText: {
    fontSize: 70,
    color: '#5000ca',
  },
});
export default App;

Finally, this is the result you will have.

Redux React Native

Let’s see how we can build the same counter application using Recoil with fewer steps.

Adding Recoil

First of all, we need to install the recoil package

Once that is done, we can freely configure the package.

Setting Up Recoil

To be able to use Recoil, we need to wrap the <App /> in RecoilRoot in the src/index.js file.

/**
 * @format
 */
import React from 'react';
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import {RecoilRoot} from 'recoil';

const RecoilApp = () => (
  <RecoilRoot>
    <App />
  </RecoilRoot>
);

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

Now we are ready to use Recoil. Let’s create our first atom and read the value from it.

Creating an anti-state atom

An atom is a piece of state. Atoms can be read from and written to any component. This also causes the component that reads the value of an atom to automatically subscribe to this atom.

Within the src/App.tsxlet’s create an atom called counterState.

import React from 'react';
import {SafeAreaView, StyleSheet, Text, View, Button} from 'react-native';
import {atom, useRecoilState} from 'recoil';

const counterState = atom({
  key: 'counterState',
  default: 0,
});
...

The key here is a unique identifier to help with atom selection. It also needs a default value — in this case, it is 0.

Next, let’s create a state that will read from the counterState. For this, we will use the useRecoilState() hook

...
const App = () => {
  const [counter, setCounter] = useRecoilState(counterState);
...

Finally, let’s create the increase and decrease buttons.

...
const App = () => {
  const [counter, setCounter] = useRecoilState(counterState);

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.counterContainer}>
        <Text style={styles.counterText}>{counter}</Text>
      </View>
      <View style={styles.buttonContainer}>
        <Button
          title="Increment"
          onPress={() => setCounter(counter + 1)}
          color="#5000ca"
        />
        <Button
          title="Decrement"
          onPress={() => setCounter(counter - 1)}
          color="#5000ca"
        />
      </View>
    </SafeAreaView>
  );
};
...

And here is the final code of the App.js file.

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow strict-local
 */

import React from 'react';
import {SafeAreaView, StyleSheet, Text, View, Button} from 'react-native';
import {atom, useRecoilState} from 'recoil';

const counterState = atom({
  key: 'counterState',
  default: 0,
});
const App = () => {
  const [counter, setCounter] = useRecoilState(counterState);

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.counterContainer}>
        <Text style={styles.counterText}>{counter}</Text>
      </View>
      <View style={styles.buttonContainer}>
        <Button
          title="Increment"
          onPress={() => setCounter(counter + 1)}
          color="#5000ca"
        />
        <Button
          title="Decrement"
          onPress={() => setCounter(counter - 1)}
          color="#5000ca"
        />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  counterContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  buttonContainer: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    width: '70%',
  },
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  counterText: {
    fontSize: 70,
    color: '#5000ca',
  },
});
export default App;

This produces a similar result with less code and a much simpler process.

React Native Magazine

Building the project with Codemagic

Codemagic helps you write CI/CD pipelines for mobile or desktop applications, including building, testing and publishing your apps. We will write a CI/CD pipeline for our counter application to build, test and publish it. Here, I will focus on Android builds.

Configure Codemagic

First of all, make sure you have an account Code magic. Then add a new application and select the repo service you use to store your source code. I’m working with GitHub here, but you can choose whatever service you want.

Type of deposit

Once that is done, you can select your project. Make sure the project type is set to React Native App.

Type of Project

The next step is to encode the environment variables for YAML configuration

    storePassword=android
    keyPassword=yourpassword
    keyAlias=androiddebugkey
    storeFile=keystore.keystore

Environmental variables

In the configuration phase of the project, you can see a tab called Environmental variables.

Create a group name called redux-recoil-app (you can change it according to your needs), and add the values ​​according to yours key.properties file.

Environmental variables

Returning to the code, create a file named codemagic.yaml at the root of your project. It should contain the following content:

workflows:
    react-native-android:
        name: React Native Android
        max_build_duration: 120
        instance_type: mac_mini
        environment:
            groups:
                - react-recoil-app 
            vars:
                CM_KEYSTORE_PASSWORD: Encrypted()
                CM_KEY_PASSWORD: Encrypted()
                CM_KEY_ALIAS: Encrypted()
            node: 16.14.0
        scripts:
            - yarn install
            - echo "sdk.dir=$HOME/programs/android-sdk-macosx" > "$CM_BUILD_DIR/android/local.properties"
            - |
                chmod -R 777 $CM_BUILD_DIR
                echo $CM_KEYSTORE | base64 --decode > $CM_BUILD_DIR/keystore.jks
                # build Android
                cd android
                ./gradlew assembleRelease                
        artifacts:
            - android/app/build/outputs/**/*.apk

You can find this file in the GitHub repository for the sample project.

React or Pushback — which should you choose for your next React Native project?

Given what we’ve seen, Recoil certainly looks a lot simpler, more elegant, and more intuitive than Redux. But Redux is the result of many years of research and development, while the infrastructure around Recoil is still in its infancy.

So, if you are working on a React Native project that will eventually grow very large, Redux is a great way to go. The coding code can be overwhelming at first, but it will help with code architecture and organization.

If you are starting a small React Native project and React.Context doesn’t work for you, Recoil is a great option. It’s a simple and easy way to manage the state of your application, and depending on your needs and knowledge of code architecture and organization, you may be able to use it to its fullest. However, you’ll need to have clear rules about how to organize Recoil’s atoms, selectors, and other features to avoid confusion when the codebase grows.

Conclusion

Thank you for reading this article. I hope it gives you a basic understanding of Redux and Recoil so you can choose the best option for your React Native project and start doing something with it on your own.

You can find the source code for the project on GitHub.


Kolawole Mangabo is a full engineer who works with Python and JavaScript. Currently busy building things in a food tech company by day and fintech products by night, he is also a tech writer who covers Django, React, and React Native on his personal blog.

Leave a Comment