Using Zustand in an Expo app

Using Zustand in an Expo app

Zustand is an excellent alternative to Redux if you want a simpler, yet powerful solution to manage state in your React projects. In this post, we'll be going over how to install and use Zustand in your Expo app.

This is the project structure for this blog post. I'm using Expo Router to utilize file-based routing. I'm also using the createBearStore from the Zustand docs for a uniform experience between the docs and this post.

root
> app
    > report-sighting
        index.tsx
    _layout.tsx
    index.tsx
> assets
> state
    index.ts
> node_modules
... rest of files

This app has two pages, index.tsx (Home) and report-sighting/index.tsx (Report Sighting). The _layout.tsx in the app directory holds a Stack layout to easily navigate between the pages.

Install Zustand

To install Zustand in your app, run the code below to add it into your Expo app.

npx expo install zustand

If you're unfamiliar with Expo libraries, using npx expo install instead of npm install might be a little odd. Using npx expo install enables Expo to pick a version of the library that is compatible with your project. One of the many ways Expo helps limit complexity in your project. Since this isn't a blog on how to use Expo specifically, I'll leave you to read any documentation you need.

Create the useBearStore hook

In our index.ts file inside the state folder, we'll create the hook to manage state.

import { create } from "zustand";

// create an interface for the bear store to implement
interface BearStore {
    bears: number;
    increasePopulation(): void;
}

// create the bear store, implementing the BearStore interface
const useBearStore = create<BearStore>((set) => ({
    // set the initial value in the store to 0 bears
    bears: 0,
    // When this function is fired off, it increases the number of bears by 1
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
}));

// export the useBearStore hook
export default useBearStore;

Here, we're creating an interface BearStore to give our useBearStore the minimum required properties required for our state-management. We'll need a number type, bears that holds the amount of bears seen and a void function increasePopulation that increments the bears count by one when called.

Display the total bear sightings

Next, in the index.tsx file in the app directory, we'll create a <View> that displays the total number of bears seen.

import { Link } from "expo-router";
import { View, Text, StyleSheet } from "react-native";
import useBearStore from "../state";

export default function Home() {
    // grab the total number of bears from the useBearStore hook
    const bears = useBearStore(({ bears }) => bears);

    // return the style for the home page
    return (
        <View style={styles.wrapper}>
            <Text style={styles.heading}>Total sightings: {bears}</Text>
            <Link style={styles.link} href="/report-sighting">
                Report a sighting
            </Link>
        </View>
    );
}

// stylesheet for the Home page
const styles = StyleSheet.create({
    wrapper: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
    },
    heading: {
        fontSize: 30,
        marginBottom: 20,
    },
    link: {
        backgroundColor: "#333",
        color: "#fff",
        paddingVertical: 10,
        paddingHorizontal: 20,
        borderRadius: 5,
    },
});

In order to display the total number of bears seen, we need to grab the bears value from the useBearStore. Here, we destructure the state object stored in useBearStore and return only the bears, which happens to be the total number of bears stored in state. We store the bears in the variable const bears. Just like any variable in React, we use it in the return value surrounded by curly brackets.

Updating the total number of bears

The last thing we need to implement (for this custom hook) is our ability to update the state. We'll be adding our code in the index.tsx file in the app/report-sighting directory.

import { View, Text, StyleSheet, Pressable } from "react-native";
import useBearStore from "../../state";

export default function ReportSighting() {
    // grab the increasePopulation function from the useBearStore hook
    const increasePopulation = useBearStore(({ increasePopulation }) => increasePopulation);

    // return the view for the Report Sighting page
    return (
        <View style={styles.wrapper}>
            <View style={styles.buttonWrapper}>
                <Pressable onPress={increasePopulation}>
                    <Text style={styles.button}>I've seen a bear!</Text>
                </Pressable>
            </View>
        </View>
    );
}

// stylesheet for the Report Sighting page
const styles = StyleSheet.create({
    wrapper: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
    },
    buttonWrapper: {
        width: "auto",
        backgroundColor: "#333",
        paddingVertical: 10,
        paddingHorizontal: 20,
        borderRadius: 5,
    },
    button: {
        color: "#fff",
    },
});

Just like when we grabbed the bears from our hook, we'll grab the increasePopulation function from useBearStore. In order to call the increasePopulation function, we have a <Pressable> element that fires off the function when it's pressed.

Wrapping up

Zustand manages state across the entire application. The project is setup with two unrelated pages, yet the state is managed across both pages. One page displays the current amount of bears seen and the other page updates the total count. This is a very simple example of how global state-management works, but hopefully you can see the power of Zustand and just how simple it is to manage state.

You can visit my GitHub if you want the full working code to play around with.