May 26, 2022

Robotic Notes

All technology News

State management in React with MobX

7 min read


According to the Documentation,

MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP).

One of the most asked questions between developers in creating modern React applications includes state management. In this tutorial, we will learn how to use MobX as a state management library for React applications. We’ll use it to manage the state, which will help us understand the concept and guidelines of using MobX.

What is MobX?

Like other similar libraries (eg, Redux, Recoil, Hook states), MobX is a state manager, but with simplicity and scalability when it comes to state management.

Mobx distinguishes between the following concepts in an application.

State
The State is the data that manages your applications. It contains different data types ranging from arrays, strings, numbers, and objects that MobX allows you to work with. All you have to do is make sure that all the properties you want to change over time are observable so MobX can track them. Below is a simple example.

1import React from "react";

2import ReactDOM from "react-dom";

3import { makeAutoObservable } from "mobx";

4import { observer } from "mobx-react";

5

6

7class Timer {

8 secondsPassed = 0;

9

10 constructor() {

11 makeAutoObservable(this);

12 }

13

14 increase() {

15 this.secondsPassed += 1;

16 }

17

18 reset() {

19 this.secondsPassed = 0;

20 }

21}

22

23const myTimer = new Timer();

24

25

26const TimerView = observer(({ timer }) => (

27 <button onClick={() => timer.reset()}>

28 Seconds passed: {timer.secondsPassed}

29 </button>

30));

31

32ReactDOM.render(<TimerView timer={myTimer} />, document.body);

33

34

35setInterval(() => {

36 myTimer.increase();

37}, 1000);

The TimeView React component wrapped around the observer will automatically detect that rendering depends on the timer.secondsPassed observable, even though this relationship is not defined explicitly.

Every event (onClick/setInterval) invokes an actionmyTimer.increase/myTimer.reset) that updates observable statemyTimer.secondsPassed). Changes in the observable state are propagated precisely to all computations and effects (TimeView) that depend on the changes made.

Action
If a state is your data, then an Action is any block of code that can change such data: User events, Backend data, etc. An action is like a person who alters data in the spreadsheet cell. In the Timer code above, we can see increase and reset methods that change the value of secondsPassed. Actions help you structure your code block and prevent you from constantly changing state when you don’t have to. Methods that modify the state are called actions in MobX.

Derivations
Whatever that has gotten from the state is known as derivation, and it exists in different forms, but we’ll be looking at the different kinds of derivations of MobX:

Computed Values
These are values ​​that can be derived from the state using a pure function. They will be updated automatically by MobX, and they are suspended when not in use. Below is an example of a Computed Value.

1class TodoList {

2 @observable todos = [];

3 @computed get unfinishedTodoCount() {

4 return this.todos.filter((todo) => !todo.finished).length;

5 }

6}

Reactions
Reactions are like computed values: they react to state changes but instead produce side effects. In React, you can turn stateless function components into reactive components by simply adding the observer function. Observer converts React function components into a derivation of data they render. MobX makes sure the components are always re-rendered when needed but not more than that. Below is an example of how an Observer function can be used:

1const Todos = observer(({ todos }) => (

2 <ul>

3 {todos.map((todo) => (

4 <Todoview ... />

5 ))}

6 </ul>

7));

Custom Reactions can be created using autorun, reactionor when.

1

2autorun(() => {

3 console.log("Tasks left: " + todos.unfinishedTodoCount);

4});

5

6

7const reaction = reaction(

8 () => todos.map((todo) => todo.title),

9 (titles) => console.log("reaction:", titles.join(", "))

10);

11

12

13async function x() {

14 await when(() => that.isVisible);

15

16}

MobX can be installed using any package manager such as npm by using the npm install -- save mobx command.

Why should you consider MobX?

The main goal of MobX is to improve state management for developers and create an easier way of managing Javascript application states with less code and boilerplates. MobX uses observable data, which helps track changes automatically, making life easier for developers.

MobX allows you to manage your application state outside of any framework. This makes code decoupled, portable, and easily testable, which is why it’s termed UNOPINIONATED.

MobX Vs. Redux / Recoil / HookState

Unlike other state managers like Redux and Easy Peasy, MobX uses multiple stores to handle the application state. You can separate the stores so all application states will be in a single store like Redux.

One of the top issues about Redux is the amount of boilerplate code that comes with it, and integration with React leads to excess boilerplates, which developers find unappealing. MobX requires little or no boilerplate code and does not require any special tools, making its setup simple and easy.

Redux is more opinionated, leading to pure functions, but MobX wins due to its unopinionated feature when it comes to scalability.

When to use MobX?

Although some developers tend to get confused about when to use which state management library for which project, let’s break it down to understand. If you want to write minimal code, with little or no boilerplate obstructing your view, or if you are trying to update a record field without any special tools, then MobX is what you should be thinking of.

Open Source Session Replay

OpenReplay is an open-source alternative to FullStory and LogRocket. It gives you full observability by replaying everything your users do on your app and showing how your stack behaves for every issue. OpenReplay is self-hosted for full control over your data.

replayer.png

Happy debugging for modern frontend teams – start monitoring your web app for free.

Building a React application with MobX.

We’ll be building a Todo Application for note keeping, and beautifying our Todo App with Framer-Motion library. We’ll be using Mobx as a state manager in our Todo App. We’ll create a store that will have a class, and that class will have a data constructor that will make the class observable to changes in state.

First, we’ll set up our environment by creating our react application with the following command on your terminal.

npx create-react-app todo-app --template typescript

Then we change our directory and install the needed dependencies before creating our components and state.

1cd todo-app

2npm install -s mobx mobx-react-lite

3npm install framer-motion

4npm install react-icons

5npm start

Create a Store Component

We’ll be creating our store.ts component in our root folder, and we are using Mobx with React Context API to make our store available to all the components.

1

2

3import { createContext, useContext } from "react";

4import todoStore from "./store/TodoStore";

5

6const store = {

7 todoStore: todoStore(),

8};

9

10export const StoreContext = createContext(store);

11

12export const useStore = () => {

13 return useContext<typeof store>(StoreContext);

14};

15

16export default store;

Create a TodoStore Component

The TodoStore.ts carries our state component. First, we create a function todoStorewhich returns makeAutoObservable (from MobX) with a list with a title and id.

1

2

3import { makeAutoObservable } from "mobx";

4

5const todoStore = () => {

6 return makeAutoObservable({

7 list: [] as { title: string; id: number }[],

8 });

9};

10

11export default todoStore;

Create a TodoForm Component

We’ll have to build a TodoForm.tsx component for Creating Todos.

1

2

3import { motion } from "framer-motion";

4import { GoPlus } from "react-icons/go";

5import { action } from "mobx";

6import { FormEvent } from "react";

7import { useStore } from "../stores";

8

9const TodoForm = () => {

10 const { todoStore } = useStore();

11 const handleSubmit = action((e: FormEvent) => {

12 e.preventDefault();

13 const formData = new FormData(e.target as HTMLFormElement);

14 const value = formData.get("title")?.toString() || "";

15 todoStore.list.push({

16 title: value,

17 id: Date.now(),

18 });

19 });

20

21 return (

22 <form className="addTodos" action="#" onSubmit={handleSubmit}>

23 <input name="title" placeholder="add text" className="todo-input" />

24

25 <motion.button

26 whileHover={{ scale: 1.1 }}

27 whileTap={{ scale: 0.9 }}

28 className="add-btn"

29 >

30 <GoPlus />

31 </motion.button>

32 </form>

33 );

34};

35

36export default TodoForm;

Create a TodoList Component

To list all our Todo’s input, we’ll have to create a TodoList.tsx component.

1

2

3import { AnimatePresence } from "framer-motion";

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

5import { useStore } from "../stores";

6import { motion } from "framer-motion";

7

8const TodoList = () => {

9 const { todoStore } = useStore();

10

11 return (

12 <motion.li

13 whileHover={{

14 scale: 0.9,

15 transition: { type: "spring", duration: 0.2 },

16 }}

17 exit={{

18 x: "-60vw",

19 scale: [1, 0],

20 transition: { duration: 0.5 },

21 backgroundColor: "rgba(255,0,0,1)",

22 }}

23 className="displaytodos"

24 >

25 {todoStore.list.map((l) => (

26 <h3 className="card" key={l.id}>

27 {l.title}

28 </h3>

29 ))}

30 </motion.li>

31 );

32};

33

34export default observer(TodoList);

Create TodoDetails Component

The TodoDetails.tsx file has little: our TodoForm and TodoList components.

1

2

3import React from "react";

4import TodoForm from "./TodoForm";

5import TodoList from "./TodoList";

6

7function TodoOverview() {

8 return (

9 <>

10 <TodoForm />

11 <TodoList />

12 </>

13 );

14}

15

16export default TodoOverview;

Create Main.css Component

The main styling is as follows.

1@import url("https://fonts.googleapis.com/css2?family=RocknRoll+One&display=swap");

2

3html {

4 line-height: 1.15;

5}

6

7* {

8 box-sizing: border-box;

9 margin: 0;

10 padding: 0;

11 font-family: "RocknRoll One", sans-serif;

12}

13

14body {

15 background: linear-gradient(

16 190deg,

17 rgb(134, 123, 205) 0%,

18 rgb(106, 90, 171) 100%

19 );

20 background-repeat: no-repeat;

21 background-size: cover;

22 background-attachment: fixed;

23 color: #222;

24 overflow: hidden;

25}

26

27.App {

28 margin-top: 3rem;

29 display: flex;

30 flex-direction: column;

31}

32

33.App h1 {

34 display: inline;

35 text-align: center;

36 margin-bottom: 2rem;

37 color: #e1ebfd;

38 text-shadow: 0 0 5px #433aa8, 3px -1px 5px #271c6c;

39}

40

41.addTodos {

42 display: flex;

43 justify-content: center;

44}

45

46.todo-input {

47 min-width: 15rem;

48 width: 40vw;

49 max-height: 2.5rem;

50 background-color: #e1ebfd;

51 border: none;

52 border-radius: 5px;

53 padding: 0.5rem 1rem;

54 align-self: center;

55}

56

57.todo-input:focus {

58 outline: none;

59 border: 2px solid rgb(67, 58, 168);

60}

61

62.add-btn {

63 margin-left: 1rem;

64 background-color: #271c6c;

65 color: #e1ebfd;

66 border-radius: 50%;

67 border: 2px solid #e1ebfd;

68 font-size: 1.5rem;

69 width: 3.2rem;

70 height: 3.2rem;

71 cursor: pointer;

72 box-shadow: 2px 4px 10px #271c6c;

73 display: flex;

74 justify-content: center;

75 align-items: center;

76}

77

78.add-btn:focus {

79 outline: none;

80}

81

82.displaytodos {

83 margin-top: 3rem;

84 display: flex;

85 flex-direction: column;

86 align-items: center;

87}

88

89.card {

90 display: flex;

91 flex-direction: column;

92 text-align: center;

93 background: rgb(180, 182, 218);

94 background: radial-gradient(

95 circle,

96 hsla(237, 34%, 78%, 0.9) 0%,

97 hsla(219, 88%, 94%, 0.9) 100%

98 );

99 margin: 0 1rem 1rem 0;

100 height: 4rem;

101 width: 18rem;

102 border-radius: 0.5rem;

103 padding: 1rem;

104 position: relative;

105}

106

107@media Screen and (max-width: 640px) {

108 .displaytodos {

109 overflow: hidden;

110 margin-top: 2rem;

111 }

112 .displaytodos ul {

113 display: flex;

114 flex-direction: column;

115 align-items: center;

116 margin-left: 0;

117 align-self: center;

118 }

119 .card {

120 margin-right: 0;

121 }

122}

Implementing Framer-Motion

Implementation of Framer-Motion (for animations controlled by motion components) in the App.tsx needs this code.

1import React from "react";

2import TodoDetails from "./components/TodoDetails";

3import "./css/main.css";

4import { motion } from "framer-motion";

5

6function App() {

7 return (

8 <div className="App">

9 <motion.h1

10 initial={{ y: -200 }}

11 animate={{ y: 0 }}

12 transition={{ type: "spring", duration: 0.5 }}

13 whileHover={{ scale: 1.1 }}

14 >

15 Todo App

16 </motion.h1>

17 <motion.div

18 initial={{ y: 1000 }}

19 animate={{ y: 0 }}

20 transition={{ type: "spring", duration: 1 }}

21 >

22 <TodoDetails />

23 </motion.div>

24 </div>

25 );

26}

27

28export default App;

Our complete app

And our Todo App seems to be working very fine, handling its inner state with MobX.

Summary

In this article, we took a tour of MobX as a React State Management library. We also learned how to use the reactive state of MobX to manage the state of an application, which was quite interesting. We integrated it with our code and Framer-Motion for animations.

Resources

The Github Repo for our Todo App can be found here, and it’s deployed on Vercel.

newsletter



Source link