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
MobX - https://mobx.js.org/
Tutorial template
To follow up the tutorail you can find template project source here.
Install MobX dependecies
For MobX in React Native we have to add following dependecies
# 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 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
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
export class RootStore {
classes: ClassesStore;
user: UserStore;
settings: SettingsStore;
...
}
Let's also add ClassesStore
base to the ./src/store/ClassesStore.ts
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.
/**
* ./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
/**
* ./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.
/**
* ./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.
/**
* ./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)
.
/**
* ./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
/**
* ./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 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
/**
* ./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. First we're creating trunk with rootStore
and AsyncStorage
/**
* ./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
/**
* ./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 to better understand it.
Don't forget ask your questoins in the comments.
Top comments (0)