Loading... Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?
Redux vs Recoil: Choosing the right state management library for React Native

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

May 2, 2022

State management is one of the most important skills every front-end or mobile app developer should master. Not only does it make the application more fluid or performant, but it also makes the code more easily maintainable when good tools are used. In the React Native ecosystem, you can find Redux, which is the most popular library used for state management, even ahead of React’s Context API. Redux is followed closely by Recoil, which (unlike Redux) is very easy to set up. Which of these two state management solutions should you choose for your next project?

In this article, we’ll compare these two state management solutions by 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 of your application states live in one container. To change the state, you’ll have to create a new state based on the current state and a requested change. This is why Redux has these three main components:

  • A store that holds all of your app’s state
  • An action, which is immutable data that describes a state change
  • A reducer that changes the app’s state using the current state and action

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

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

Recoil architecture

Recoil 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. Recoil has two main components — atoms and selectors.

Atoms are units of the state. They are also updatable and subscribable. This means that when an atom is updated, each subscribed component is re-rendered with the new value.

Selectors are pure functions that accept atoms or other selectors as arguments. When these upstream atoms or selectors are updated, the selector function is re-evaluated.

Components can subscribe to selectors just like they can to atoms. They are then re-rendered when a selector changes. Selectors can transform the atom state either synchronously or asynchronously.

Redux vs Recoil: Fight

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’ll need to set up a project. We’ll be making a mobile application using React Native. So, make sure you have your environment ready for React Native. In this example, I am working on a Linux machine.

npx react-native init ReduxVsRecoil

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

Adding Redux

In this step, we’ll be adding Redux packages.

yarn add redux react-redux

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

Adding reducers

As stated earlier, in Redux, actions are objects that describe what has to be done.

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

To make this clearer, let’s create a folder called store in the src directory. This directory will contain all the actions and reducers we’ll be writing. In this directory, 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 hard-coded to 0. Next, we need to register the reducer. For this, Redux provides the helper function combineReducers. This function will turn all reducers into a single reducer that can be passed to the createStore API. We’ll talk more about this API in the next sections. For now, inside the store directory, let’s create a file called reducers.js and implement the combineReducers function 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 has to be done. Let’s add the action that will increment our value. To get started, create a file called actions.js in the store directory.

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. Inside 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 using the createStore API. We can now register the store within our React Native application.

Wrapping the React Native project

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

We need to import Provider from react-redux and wrap it around the entire 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 must 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 action dispatching: 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’ll 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.

yarn add recoil

Once that’s done, we can freely configure the package.

Configuring 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 a counterState atom

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

Inside the src/App.tsx, let’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 ID to help with atom selection. It also needs a default value — in this case, it’s 0.

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

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

Finally, let’s create the increment and decrement 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’s 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.

Recoil React Native

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’ll be writing a CI/CD pipeline for our counter application to build, test, and publish it. Here, I’ll focus on Android builds.

Setting up Codemagic

First of all, make sure you have an account on Codemagic. Then add a new application and select the repo service you use to save your source code. I am working with GitHub here, but you can choose whichever service you want.

Repository type

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

Type of Project

The next step is to encrypt the environment variables for YAML configuration.

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

Environment variables

In the project configuration stage, you can see a tab named Environment variables.

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

Environment variables

Going back 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 repo for the sample project.

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

Given what we’ve seen, Recoil definitely looks much more simple, elegant, and 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 boilerplate code can be overwhelming at first, but it’ll 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 an excellent option. It’s a simple and easy way to manage your application’s state, 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 on how to organize the atoms, selectors, and other features of Recoil to avoid confusion when the codebase grows larger.

Conclusion

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

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


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

Related articles

Latest articles

Show more posts