Hacker News new | ask | show | jobs
by mistersys 1913 days ago
Yeah this is the problem with the React ecosystem. I'm intimately familiar with this stack and used to use redux with "duck" modules professionally, complete with sagas and epics, and rxjs and the whole kitten, so it's not that I don't understand all the terms and am just overwhelmed. It's just that once you stop using Redux, it becomes so much easier to build and manage your app, and you don't even realize how much you're complicating everything by using sagas and actions creators and reducers and selectors and.. and.. and.

You don't even realize how bad the boilerplate is until it's all gone. With svelte and some writable and readable stores, I can build apps in a fraction of the time because every time I want to make a change I don't have to start with an action creator, than integrate that into the reducer, then write a selector, then pull it into my component. Oh I guess I need to write an epic with rxjs to fetch the data.

Svelte has 2 primitives that replace all of this: readable and writable. You can create a writable store and you call `.update` or `.set` like react setState. Want to separate your update logic from your component logic like in redux? Easy, just export functions to update the store instead of calling `.update` in your components.

Then, there's readable. Now, readable can be used like a reselect selector, but it's also far more powerful. You can subscribe to changes from a writable, or even other readable stores. You can create a readable that fetches your data. You can create a readable that subscribes to firestore collection or document. You can make a readable that combines data from multiple sources. It's all clear and composable. And I didn't have to write an action creator with an action constant called `LOADING_PRODUCTS`, and one called `LOADED_PRODUCTS`, and then one called `UPDATED_PRODUCTS`, and for good measure we should also create an action "PRODUCTS_REQUEST_FAILED" that we'll emit from the epic.

6 comments

> Svelte has 2 primitives that replace all of this: readable and writable. You can create a writable store and you call `.update` or `.set` like react setState. Want to separate your update logic from your component logic like in redux? Easy, just export functions to update the store instead of calling `.update` in your components.

This sounds very similar to the experience of using MobX. I evangelize it as an alternative to Redux every chance I get; it's incredible how practical and pleasant it is to work with.

I actually came to this thread expecting more people to call out MobX. When I use MobX with react, I find it to be as simple and _fun_ as the author finds svelte. Most of the problems people tend to raise with React are problems that are solved by observable state objects.

I also evangelize it whenever I can -- I don't want MobX to be forever doomed to its status as a cult hit!

It's baffling to me how distant of a second it is in terms of popularity. Instead of forcing you to contort your program into a special paradigm in order to deal with reactivity in a sane way, it simply makes reactivity a non-concern (while remaining pretty simple and predictable when you do actually care to dig beneath the magic). You can write code in a way that's natural and simply not think about reactivity most of the time, and you'll even get better performance in many cases because of the highly-granular pub/sub that it sets up automatically. I cannot praise this paradigm enough.
And then there's Vue.js which essentially has MobX functionality built in. When you want to update the state, you just...update the state, which can be a plain JS object that knows nothing about the front-end framework. I've used it for several medium-size projects and haven't gotten close to needing anything like Redux or Vuex.
Yeah, although when I played with Vue a couple years ago it seemed like it wasn't quite as powerful as MobX (particularly when you start stacking up a graph of computed values, or when you want to create side-effects of your own). But definitely a similar idea.
I was a fan of MobX+React until I to a look a Vue when version 3 came out. Now I'm a Vue fan and what I was doing before feels like overkill.
It is also fucking magic. I love it when it works, but I have no idea why it doesn’t when it breaks. The same is not true for Redux, which is so simple I can build a house with it.
The "magic" is very simple and understandable if you read the docs; I fairly quickly gained an understanding of it that allowed me to dig into the odd performance issue or bug without much trouble

1) Observables are properties on objects; when their getter is called, it becomes "tracked" (subscribed to) by the currently-running tracked function. When their setter is called, it publishes to all subscribers. This happens through JS proxies for plain objects, and class decorators for class instances.

2) A tracked function is anything that must be re-evaluated when an observable it depends on is modified. This includes React render functions, computed functions, and any other side-effects you've created via autorun(), reaction(), etc. Though the idea is to avoid side-effects, so 90% of the time it's just render functions and computed functions. In these cases it amounts to a cache-clear on a pure function: "the return value will be different, so re-evaluate it and use the new return value". A computed function is an observable property getter that's also a tracked function (it doesn't have a corresponding setter; instead, it publishes when it's published to).

3) Tracking works by a) marking global state before a tracked function is run, b) making note of any observables that report back during that time, c) clearing the global state (and subscribing to the observables) when the tracked function completes. Because this is done temporally based on before/after the function has executed, any nested function calls get treated exactly the same as the top-level function call. There's no special magic necessary for that.

That's about all there is to it. Everything else in the library is just another permutation on these concepts.

Great explanation! And keep fighting the good fight pushing MobX ;) Pretty much everyone I know who has tried it has ended up loving it, but it can be hard to convince people to give it a go (because of FUD around "mutability" or "magic" or whatever...).
By the way this conversation inspired me to make this: https://github.com/brundonsmith/crowdx

It's a heavily-commented, minimal reproduction of MobX's core functionality for the purpose of understanding

Wow, nice work! I’ll take a proper look at this later today, thanks for sharing :)
I also don't like having all those constants like LOADING_PRODUCTS ...

And I don't have them, instead this is how you create a type safes actions with typesafe-actions package.

  export const signIn = {
    request: createAction("auth/signin/request")(),
    success: createAction("auth/signin/success")<{ user: User }>(),
    error: createAction("auth/signin/error")<Error>(),
  };
getType is a function from typesafe-actions as well.

Not trying to sell you on Redux, just to address your point about the constants.

About having to write many Epics, well, end of the day, all the Epics have this shape.

  export const signInEpic = (action$: Observable<Action>) =>
    action$.pipe(
      filter(isActionOf(signIn.request)),
      switchMap(() =>
        from(signInLogic()).pipe(
          map((user: User) => {
            return signIn.success({ user });
          }),
          catchError((error) => of(signIn.error(error)))
        )
      )
    );

isActionOf is another typesafe-actions function...

I'm not claiming it's superior approach or faster, it's an approach that worked for me for medium/large apps, to collaborate with other developers.

I'm pretty sure you are right about the Svelte cool things you saying I wish I had more time to look into it.

We specifically recommend using our official Redux Toolkit package, rather than `typesafe-actions`. RTK is already written in TS and designed for a solid TS usage experience:

https://redux-toolkit.js.org

Redux Toolkit is great and removes most of the usual Redux boilerplate which seems to be the most often used argument against Redux. Additionally, I usually use a function which uses createEntityAdapter, createSlice and createAsyncThunk methods to create Ducks bundles for each REST API resource automatically. As a result I get all async action creators, reducers and basic selectors for some REST API resource with a couple of lines of code.
If you like the stuff in RTK so far, I think you're going to like our upcoming "RTK Query" API, which adds a React Query-inspired data fetching abstraction:

https://rtk-query-docs.netlify.app

We're working on finalizing that API and will be merging the functionality and docs back into RTK itself for an upcoming RTK release:

https://github.com/rtk-incubator/rtk-query/issues/175

RTK Query looks great, I'll check it out.
thanks! I resisted redux-toolkit when it came out, but looking at the website, it might be the time to update the stack :)
>You don't even realize how bad the boilerplate is until it's all gone

I 100% agree with this, I also started out working with Redux in the fairly early days of React and while I do feel that I did learn a lot from it and have used Redux-inspired patterns for certain parts of applications since then, the productivity gains I got from switching to MobX for state management can't be overstated.

Sure, you could argue it's less "clean" or whatever, but honestly 99% of the time it just works, and I'm happy to have the 1% of time where you're debugging where you forgot to add "observable" or whatever in exchange for the 99% productivity boost.

Svelte does look really interesting and follows a similar kind of model to how I work with React and MobX, and does away with some boilerplate/gotchas.

My main concern using it would be around maturity of libraries... while it does seem pretty batteries-included, I have got used to being able to just pull in a library for x, y or z in React and I'd be worried if I started a big Svelte project, I might find myself kicking myself down the line e.g. if I need some component (data table or whatever) that has a mature option available in React. Perhaps people who've worked with Svelte professionally can comment on whether this is a valid concern?

I do get the arguments that you're making here about Svelte, and don't disagree. That said, "Modern Redux" code is very different than what you've probably seen even just a couple years ago. We've introduced newer APIs like Redux Toolkit, which is a set of utilities that provide a light abstraction to simplify the most common Redux tasks, and the React-Redux hooks API, which is generally easier to use than the traditional connect API.

If you get a chance, I strongly recommend reading through the newly rewritten official tutorials in the Redux docs, which have been specifically designed to show both how Redux works and show our recommended practices:

- "Redux Essentials" tutorial [0]: teaches "how to use Redux, the right way", by building a real-world app using Redux Toolkit

- "Redux Fundamentals" tutorial [1]: teaches "how Redux works, from the bottom up", by showing how to write Redux code by hand and why standard usage patterns exist, and how Redux Toolkit simplifies those patterns

The older patterns shown in almost all other tutorials on the internet are still valid, but not how we recommend writing Redux code today.

You should also read through the Redux "Style Guide" docs page [2], which explains our recommended patterns and best practices. Following those will result in better and more maintainable Redux apps.

Finally, we've got an upcoming "RTK Query" API that we're finalizing and adding to Redux Toolkit (inspired by tools like React Query and Apollo) , which should drastically simplify the data fetching story for Redux apps.

[0] https://redux.js.org/tutorials/essentials/part-1-overview-co...

[1] https://redux.js.org/tutorials/fundamentals/part-1-overview

[2] https://redux.js.org/style-guide/style-guide

[3] https://rtk-query-docs.netlify.app

I can agree on redux hooks being a better alternative to the whole mapToState cycle. But if you go into the new tutorials, they quickly devolve into the same old: https://redux.js.org/tutorials/fundamentals/part-8-modern-re...

Oh, look, slices, and thunks, and asyncThunks, and extraReducers, and adapters, and fifteen files to tie all this together for every small piece of api or data that you want to send around.

Most of it just comes from the fact that Javascript doesn't have a good built-in way of doing these cycles fo data/store updates. And still, I don't know where the sweet spot of Redux it: it's an overkill for small apps, it's unmanageable in big apps.

However, I will concede that the new hooks like `useDispatch` are nice

I'm not sure how you're looking at that docs page and saying that it "devolves into the same old". That page explicitly shows how RTK simplifies existing Redux patterns. Yes, concepts like "thunks" and "reducers" still exist. Yes, there are new APIs like `createSlice` and `createEntityAdapter`, which have their own options. That's because we've seen how people are using Redux, and have built those APIs to help solve the use cases people are dealing with. But no, the code you write today is drastically different, and we've gotten tons of positive feedback about how much people enjoy using RTK.

It's also definitely _not_ "15 files to tie this all together". In fact, we specifically recommend writing all the Redux logic for a given feature in a single "slice file":

- https://redux.js.org/style-guide/style-guide#structure-files...

- https://redux.js.org/tutorials/essentials/part-2-app-structu...

> That page explicitly shows how RTK simplifies existing Redux patterns.

The page is a tutorial for a TODO list with only the simplest and the happiest paths. It's not a forum just because you called it a forum, by the way.

OT: I wish the JS world would stop doing TODO lists. A TODO list is a glorified `Array.push`. If it takes your library/framework 10 000 words to explain how to do Array.push, it doesn't mean that a TODO list is a good example on which to base a tutorial. It means your library sucks. If you want to show how great you library is, implement this: https://tonsky.me/blog/datascript-chat/ (demo: http://tonsky.me/datascript-chat/) and manage to describe it in as many words as on that page.

Back to the topic at hand.

The problem with Redux is: it's concept is very simple. The implementation is a poorly specified DSL with isolated parts that barely fit each other, if at all. And even with all the simplifications it can barely manage to make a TODO list with happy paths.

Here's where it all falls apart:

- It looks like I can't use async functions in `createSlice`. I must use a separate `createAsyncThunk`. Because reasons. Because in a world where every single app needs async data fetching "if you want to have async logic interact with the store by dispatching or checking the current store state? That's where Redux middleware come in.": https://redux.js.org/tutorials/essentials/part-5-async-logic

So, suddenly, it's not "we've simplified everything", it's the same old: we need thunks, and middlewares, and whatnot to do data fetching.

Note in that page how `createSlice` (which is an action creator with reducers and stuff) and `createAsyncThunk` (which is an action creator for which you have to provide a store separately, and reducers to that store separately) don't ever fit with each other except through additional boilerplate code. This is especially visible in `features/posts/postsSlice.js` towards the end of the page

And why does everything need a dispatch anyway? Can't everything already be wrapped in a dispatch so that you just call a function?

- There's almost nothing about errors that will inevitably pop up.

Almost no part of the tutorial ever shows how to deal with errors except the "ah, it will magically dispatch an error message to the store, we store the message on the store".

What happens if I run into an error in a "reducer" (which is reducer-slash-action-creator) in `createSlice` and want to dispatch some action? Oh, wait, you can't dispatch actions from a reducer.

What happens if I run into an error in an action created with `createAsyncThunk` and I need to not only return an error, but dispatch some other actions?

- All this is just so, so badly specified. Everything is hidden behind "detailed explanations" that actually try to explain what's going on

For example, the fact that createAsyncThunk magically creates `.pending`, `.fulfilled` and `.rejected` on an action is not explained except sort of in passing.

And there's suddenly a magical `unwrapResult` function that's not even mentioned anywhere. What is the result of an action? It's not in Part 1 or Redux Concepts and not in Part 1 of Redux Fundamentals. I very quickly scanned the rest of the War and Peace novel, and I can't say I found it.

And there's more. I just can't quickly read and grok the 20 volumes of dense prose just to figure out how to push data into an array.

"Async logic and data fetching are always a complex topic." Nope. It's not. Redux makes it a complex topic.

---

So yeah, it all very quickly devolves into the very same "15 different files for every small action". You may collocate some of them in a single file, but that doesn't make them even closely related to each other in any meaningful way.

That's why I said, "I don't know where the sweet spot of Redux it: it's an overkill for small apps, it's unmanageable in big apps."

And it's not all bad. The biggest step towards simplifying Redux have been hooks. `useDispatch` and `useSelector` are really good. Even though we have to admit that they are solving the issues that Redux introduced in the first place :)

Your comment really cuts through to the truth about Redux, and gave me flashbacks to my “what the hell” moment the first time I needed to perform a fetch, and the subsequent painful mentoring of a junior dev who just couldn’t get it. Two other junior devs quit because they inherited a React codebase using RX. I couldn’t really blame them.
To be fair, you're criticizing Redux more than React. And even then, you're criticizing a specific pattern of using Redux: redux-toolkit melts away all the boilerplate you're talking about, and you could use something else than Redux if you wanted.

I agree that React folks (me included) tend to reach out to Redux much too soon, though.

You should try redux toolkit