React Native MobX QuickStart
Tags: reactnative, mobx, persist, typescript
Published: 2021-12-04 19:00:02


This tutorial isn't suppose to dive into MobX concepts, it's rather practical guide how to setup your first MobX storage. 

## References
Source code on the GitHub - [react-native-mobx-tutorial](https://github.com/dimaportenko/react-native-mobx-tutorial)
MobX - [https://mobx.js.org/](https://mobx.js.org/)

{% youtube N7QbRgaIdEY %}

## Tutorial template
To follow up the tutorail you can find template project source [here](https://github.com/dimaportenko/react-native-mobx-tutorial/tree/template-branch). 

## Install MobX dependecies
For MobX in React Native we have to add following dependecies
```bash
# with npm
npm install --save mobx mobx-react-lite

# or with yarn
yarn add mobx mobx-react-lite
```

## Add store classes
MobX suggests to use [JavaScript classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) to store your data and keep mutation logic near by. It's also possible to use objects but we'll stay with default solution. 

Let's start with RootStore class in the `./src/store/index.ts`
```typescript
export class RootStore {
  classes: ClassesStore;

  constructor() {
    this.classes = new ClassesStore();
  }
}

export const rootStore = new RootStore();
```
As you can see `RootStore` is just a container for other stores. In our case we have only `ClassesStore`, but in real world we'll have more. Like 
```typescript
export class RootStore {
  classes: ClassesStore;
  user: UserStore;
  settings: SettingsStore;
  ...
}

```
Let's also add `ClassesStore` base to the `./src/store/ClassesStore.ts`
```typescript
export type ClassItemType = {
  id: number;
  title: string;
}

export class ClassesStore {
  items: ClassItemType[] = [];
}

```

## Place store to React Context
To be able easily access store data inside React components we wrap our `rootStore` instance with React Context. 

```typescript
/** 
 * ./src/store/index.ts 
 **/
import { createContext, useContext } from "react";

...

export const StoreContext = createContext(rootStore);
export const StoreProvider = StoreContext.Provider;
export const useStore = () => useContext(StoreContext);

```
And wrap our main app component with `StoreProvider`

```typescript
/** 
 * ./App.ts 
 **/

...

import { rootStore, StoreProvider } from "./src/store";

export default function App() {
 ...
  
  return (
    <StoreProvider value={rootStore}>
      <PaperProvider theme={theme}>
        <SafeAreaProvider>
          <Navigation theme={theme} />
          <StatusBar style={isDark ? "light" : "dark"} />
        </SafeAreaProvider>
      </PaperProvider>
    </StoreProvider>
  );
}
```

## Access store data in the component
Now we can easily access our class store items in the component with `useStore` hook. 
```typescript
/** 
 * ./src/screens/HomeScreen.tsx 
 **/

...

export const HomeScreen = ({navigation}: Props) => {
  const rootStore = useStore();
  
  return (
    <View style={styles.container}>
      <ScrollView>
        {rootStore.classes.items.map((item) => {
          return (
            <View key={`${item.id}`} style={{marginBottom: 15}}>
              <ClassItem title={item.title} onPress={() => {
                navigation.navigate(routes.EDIT_CLASS, {
                  classItem: item
                })
              }}/>
            </View>
          );
        })}
      </ScrollView>
      ...
    </View>
  );
}
```

## Mutate store logic
And here the thing I like a lot about MobX. We add our mutation logic in the same class we keep our data. Look how we can update and delete `ClassesStore` items.
```typescript
/** 
 * ./src/store/ClassesStore.ts
 **/


export class ClassesStore {
  items: ClassItemType[] = [];

  ...

  updateItem(newItem: ClassItemType) {
    const foundItem = this.items.find(item => item.id === newItem.id);
    if (foundItem) {
      this.items = this.items.map(item => {
        if (item.id === newItem.id) {
          return newItem;
        }
        return item;
      })
    } else {
      this.items = [...this.items, newItem];
    }
  }

  deleteItem(itemToRemove: ClassItemType) {
    this.items = this.items.filter(item => item.id !== itemToRemove.id);
  }
}
```

## Use state actions in the EditClassScreen
Simple as it is. Take our item from navigation params or create new if empty. Get `classes` from `useStore` hook. On delete function we run `classes.deleteItem(item)` and on save we do `classes.updateItem(newItem)`. 

```typescript
/** 
 * ./src/screens/EditClassScreen.tsx 
 **/

...

export const EditClassScreen =
  ({navigation, route}: EditClassScreenProps) => {
    const item = route.params?.classItem ?? {
      id: new Date().getTime(),
      title: '',
    };

    const { classes } = useStore();

    ...

    const onDelete = () => {
      classes.deleteItem(item);
      navigation.goBack();
    }

    const onSave = () => {
      if (!title) {
        setError(true);
      } else {
        classes.updateItem({
          id: item.id,
          title,
        })
        navigation.goBack();
      }
    };

    ...
;
```

## Make store observable with MobX
Now if you try to make changes to our items on the edit screen and then back to home you will not see any changes. To make it works we need to integrate MobX into our store. So we make our `ClassesStore` for observable with `makeAutoObservable` function

```typescript
/** 
 * ./src/store/ClassesStore.ts
 **/

...
import { makeAutoObservable } from "mobx";

export class ClassesStore {
  ...

  constructor() {
    makeAutoObservable(this);
  }
  
  ...
}

```
To understand better what we've just done I recomment to take a look [observable state](https://mobx.js.org/observable-state.html) chapter in the documentation. 

## Observe store changes in the component 
The only thing we need to do to be able observe changes we have to wrap our component with `observer` function like this
```typescript
/** 
 * ./src/screens/HomeScreen.tsx 
 **/

...
import { observer } from "mobx-react-lite";
...

export const HomeScreen = observer(({navigation}: Props) => {
  ...
});
```

## Persist store state
Last thing we want to persist our state on the app restart. To do it we'll use [mobx-sync](https://github.com/acrazing/mobx-sync). First we're creating trunk with `rootStore` and `AsyncStorage`

```typescript
/** 
 * ./src/store/index.ts 
 **/
...
import { AsyncTrunk } from 'mobx-sync';
import AsyncStorage from '@react-native-async-storage/async-storage';

...

export const trunk = new AsyncTrunk(rootStore, {
  storage: AsyncStorage,
})

...
```

Then rehydrate trunk before start render our app components 

```typescript
/** 
 * ./App.tsx 
 **/
...
import { rootStore, StoreProvider, trunk } from "./src/store";

export default function App() {
  ...

  useEffect(() => {
    const rehydrate = async () => {
      await trunk.init();
      setIsStoreLoaded(true);
    }
    rehydrate();
  }, []);


  if (!isLoadingComplete || !isStoreLoaded) {
    return (
      <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
        <ActivityIndicator size="large" />
      </View>
    );
  } else {
    return (
      ...
      {/** Our app components tree here */}
      ...
    );
  }
}
```

## Conclusion

This was practical guide how to start with MobX in React Native. We skipped MobX conceps so I defently recommend to check [official docs](https://mobx.js.org/README.html) to better understand it. 

Don't forget ask your questoins in the comments.