Redux is a boilerplate, Mobx is not! But there is a nuance

IT community

IT community

Last week I took part in a Frontend conference for the first time, where one of the speakers told how successfully his team moved from Redux to Mobx. He called the main advantage the absence of a boilerplate and speeding up development by one and a half times.

I've read several articles and watched other reports where everyone says that Mobx is better than Redux. This may be true, but I don’t understand why Redux is always compared and not Redux-Toolkit. Let's try to constructively see if Mobx is really as good as they say it is.

The main argument of Mobx adherents goes something like this

When developing with Redux, you have to write boilerplate code to make everything work. You need to write actions and selectors.

As an example, let's write the simplest logic to request posts and change the counter and see how many lines of code we can save.

import { makeAutoObservable } from "mobx";
import { IPromiseBasedObservable, fromPromise } from "mobx-utils";

/* Типизация */
const PostListSchema = z.object({
  id: z.number(),
  title: z.string(),
  description: z.string(),
  tag: z.string(),
  tags: z.array(z.string()),
  image: z.string(),
  progress: z.number(),
  progressTotal: z.number()
})

type PostListModel = z.infer<typeof PostListSchema>

/* Запрос на получение данных */
export const fetchPostList = async (limit: number) => {
  try {
    const response = await _api.get<PostListModel[]>(`api/posts`)

    if (!response.data) {
      throw new Error("Ошибка")
    }

    return response.data.data
  } catch {
    throw new Error("Ошибка")
  }
}


/* Создание стора */
class PostListStore {
    posts?: IPromiseBasedObservable<PostListModel[]>
    counter: 0

    constructor() {
        makeAutoObservable(this)
    }

    incrementCounter = () => {
      this.counter += 1
    }

    decrementCounter = () => {
      this.counter -= 1
    }

    fetchCoursesData = (limit: number) => {
        this.courses = fromPromise(fetchPostList(limit))
    }
}

export const postListStore = new PostListStore()

Now let's try to write the same logic in Redux-Toolkit. But to avoid bias in our assessment, let's ask chatGPT to write the code for us.

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AppThunk } from "./store";
import { fetchPostList, PostListModel } from "./api";

/* Типизация */
interface PostListState {
  posts: PostListModel[] | null;
  loading: boolean;
  error: string | null;
  counter: number;
}

const initialState: PostListState = {
  posts: null,
  loading: false,
  error: null,
  counter: 0,
};

/* Создание слайса */
const postListSlice = createSlice({
  name: "postList",
  initialState,
  reducers: {
    incrementCounter(state) {
      state.counter += 1;
    },
    decrementCounter(state) {
      state.counter -= 1;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPostList.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchPostList.fulfilled, (state, action: PayloadAction<PostListModel[]>) => {
        state.loading = false;
        state.posts = action.payload;
      })
      .addCase(fetchPostList.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message ?? "Ошибка";
      });
  },
});

export const { incrementCounter, decrementCounter } = postListSlice.actions;

export default postListSlice.reducer;

/* Запрос на получение данных */
export const fetchPostListAsync = (limit: number): AppThunk => async (dispatch) => {
  try {
    const response = await axios.get("/api/posts")

    if (!response.data) {
      throw new Error("Ошибка")
    }

    return response.data
  } catch {
    throw new Error("Ошибка")
  }
};

The implementation of the code is very similar, the only thing is that in mobx it looks a little simpler. However, the total difference is 10 lines, I can’t call it a boilerplate. There is no need to write actions either; the toolkit does everything for us.

As an experiment, let's ask chatGPT to write a PostList component using Mobx and Redux-Toolkit.

/* Код с использованием Mobx */

import React, { useEffect } from "react";
import { observer } from "mobx-react-lite";
import { postListStore } from "../stores/postListStore";
import { IPromiseBasedObservableState } from "mobx-utils";

const PostListMobX: React.FC = observer(() => {
  useEffect(() => {
    postListStore.fetchCoursesData(10); // Загружаем посты при монтировании компонента
  }, []);

  const { state } = postListStore.posts ?? {};

  switch (state) {
    case "pending":
      return <div>Loading...</div>;
    case "rejected":
      return <div>Error: Failed to fetch posts</div>;
    case "fulfilled":
      return (
        <div>
          {postListStore.posts?.value.map((post) => (
            <div key={post.id}>
              <h2>{post.title}</h2>
              <p>{post.description}</p>
              {/* Другие поля поста */}
            </div>
          ))}
        </div>
      );
    default:
      return null;
  }
});

export default PostListMobX;

It may be worth noting that GPT had difficulties with the code for Mobx and was able to get the correct result only on the fourth attempt.

/* Код с использованием Redux-toolkit */

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../store";
import { fetchPostListAsync } from "../postListSlice";

const PostListRedux: React.FC = () => {
  const dispatch = useDispatch();
  const { posts, loading, error } = useSelector((state: RootState) => state.postList);

  useEffect(() => {
    dispatch(fetchPostListAsync(10)); // Загружаем посты при монтировании компонента
  }, [dispatch]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!posts) return null;

  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.description}</p>
          {/* Другие поля поста */}
        </div>
      ))}
    </div>
  );
};

export default PostListRedux;

Again, the result is approximately the same in terms of the amount of code. However, the solution using Redux-Toolkit looks simpler.

In my opinion, the very comparison of the non-Toolkit version with Mobx is extremely strange. I think this comparison was relevant in 2020, maybe, but certainly not in 2024. For myself, I will still conclude that both tools do not force the developer to write a “tone” of boilerplate code.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *