6. async-states vs react-query vs redux vs recoil

Mohamed EL AYADI
6 min readFeb 20, 2023

--

In this section, we will briefly compare the library with other tools such as redux and react-query

This post is a part of the following blog post series:

  1. async-states: the state management library
  2. async-states: Using react
  3. async-states: The source object
  4. async-states: The producer
  5. async-states: Advanced concepts
  6. async-states: vs react-query vs redux vs recoil
  7. react-async-states: SSR

Plan

  • Introduction
  • Vs redux
  • Vs recoil
  • Vs jotai suite
  • Vs react-query
  • Conclusion

Introduction

This is the section that I didn’t want to make the most! I hate comparing anything with my things, because I fear to be biased with my emotions and also I will hate other library maintainers thinking that I am mean or I want to show off and target their libraries weaknesses.

So, If you are one of the library’s maintainers in the following list, nothing is personal and I mostly learned a thing or two from you, so basically I have much respect for almost all of you. I will try to make it brief and quick! If you are a super-fan of a library, I don’t seek to replace it nor this is an invitation to use my library!

Vs redux

Our good old friend, the one we used gladly and hated the most later, then evolved to be good again.

I won’t tackle redux, no library can!

Because of one thing: its model.

Event driven programming is a concept with valid use cases and many benefits (and also cons!).

The main difference between redux and async-states is the model itself:

Store type and access

  • redux uses a global store and several reducers that tackle on “less” global properties, while passing every action to all reducers and all middlewares (o(x * n) x being how many middlewares and reducers).
  • async-states has decentralized states identified by their key (name) and can be accessed through the source and manipulated directly: their run only affects them (o(1))

Paradigm

Both can perform declarative and imperative code.

Cache, cancellations, retry, debounce and throttle

  • redux can have cache through RTQ or handmade abstraction
  • async-states has built-in multi-cache support that automatically attempts the headers.cache-control maxAge value (support cache is enabled for a given piece of state)
  • Retries are possible through RTK (I presume) or via handmade abstraction
  • async-states has built-in for configurable retry features
  • Cancellations aren’t easy in redux
  • Cancellations were in the initial design of async-states

And many more…

I am a 6 years redux user, I maintained of thousands of components using all forms of redux and I cannot talk more about it. Redux is fine, although, you might not need it.

Vs recoil

I remember you very well recoil, the day you were introduced I was all joy, I’ve read the whole codebase that night and couldn’t sleep before doing so.

I was one of the very early people to detect that recoil doesn’t totally respects react, and even tried a feature suggestion that came out.

But then the fact that this is still open up to the time of writing this post doesn’t sound good news!

recoil has an atom system similar to the Source in async-states, but less powerful and very limited in comparison. Also, it introduces many hooks and concepts that you will struggle to get a common knowledge on the team.

Vs jotai suite

jotai suite is lightweight implementation of state sharing, it is very small and limited compared to async-states. I don’t believe comparing them will be fair.

Vs react-query

I wouldn’t feel okay to compare to react-query which a very successful library that everybody knows, with more than 1.5M+ downloads per week and many tech celebrities promote it and more than 570 contributors.

I know how to respect people’s efforts at maximum, I’m not the kind that will write the use this instead of that posts! But not to leave this section empty, here is my take:

react-query is like a simple abstraction on top of async-states

Like I said, I won’t enter in the details of react-query, because I promised one of its maintainers not to be involved in their affairs anymore; because I tried to push it further and there was an unexplained resistance. Even if they know it is important and interesting!

Below, how you can mimic react-query‘s abstraction with react-async-states (I am not sure if it will also result in less bundle size!)

This is far from being complete or serious! This is a proof of concept that may get serious and we’ll have one single migration guide: replace a package name with another! Without loosing anything, while also adding GREAT and UNMATCHABLE power. But this is something I have ZERO desire to do.

function useQuery<TQueryFnData, E, T, TQueryData, K extends QueryKey, R>(
options: Options<T, E, K>
): Result<T, E> {
let mappedConfig = buildUseAsyncStateConfig<T, E, R, K>(options);
let result = useAsyncState(mappedConfig.config, mappedConfig.deps);
refreshProducerAndConfig(result, mappedConfig, options);
return mapResult(result, options);
}

Where the static configuration could be as simple as:

let key = JSON.stringify(options.queryKey);
let producer: Producer<T, E, R> = options.queryFn;
let config: ProducerConfig<T, E, R> = {
initialValue: options.initialData,
cacheConfig: {
enabled: options.cacheTime > 0,
getDeadline: () => options.cacheTime,
},
}
if (options.retry) {
config.retryConfig = {
enabled: true,
backoff: options.retryDelay,
retry: typeof options.retry === "function" ? options.retry : true,
maxAttempts: typeof options.retry === "number" ? options.retry : 2,
};
}
let mixedConfig: MixedConfig<T, E, R, State<T, E, R>> = {
key,
producer,
lazy: false,
condition: options.enabled !== undefined ? options.enabled : true,
...config,
}

And we can refresh the configuration via:

function refreshProducerAndConfig<T, E, R, S, K extends QueryKey>(
result: UseAsyncState<T, E, R, S>,
mappedConfig: MappedConfig<T, E, R, S>,
options: Options<T, E, K>
): void {
result.source!.patchConfig(mappedConfig.producerConfig);
result.onChange([
({state}) => onQueryStateChange(state, options)
]);
if (options.queryFn) {
result.source!.replaceProducer(options.queryFn);
}
}
function onQueryStateChange<T, E, R, K extends QueryKey>(
state: State<T, E, R>,
options: Options<T, E, K>
) {
if (state.status === Status.success) {
options.onSuccess?.(state.data);
options.onSettled?.(state.data);
}
if (state.status === Status.error) {
options.onError?.(state.data);
options.onSettled?.(state.data);
}
}

The rest is mapping the result and attaching events that can be host environment specific like: refetchOnFocus; refetchInterval and so on. These are simple things for async-states and there is another API for that which is called events.

And guess what, useAsyncState uses 1 useContext , 2 useState and 2 useEffect that are all optimized to quit early. In development mode, useAsyncState is as fast as useSyncExternalStore !

The thing I dislike the most about react-query is that after the very early successful days, its API was bloated with things that doesn’t make any sense, just because people want the library to solve some weird things and because that’s a big name that got a commit that adds something that shouldn’t exist.

Conclusion

In the first post of this blog post series, I talked about state managers not being equal, which should be clear to you right now.

All state managers … are not created equal!

async-states is a one-man effort from his personal time. Everyone who saw it until now said that it is mind blowing and surpasses everything they used before, but it would be difficult to show it to management and use it at work etc (blah blah)... For this:

Adoption won’t start on its own! You should do it! Cowards!

If you want my point of view, I personally use the library in production at scale in critical environments!

Finally, I really enjoyed every step of creating this library and how much I learned along the way, such as:

  • Designing the API from the start
  • Coding and iterating, I rewrote the library’s internals at least 7 confirmed times
  • Was written in js then transformed to ts
  • From one single package to a large monorepo with several libraries
  • npm, yarn and pnpm were all used
  • Mastering webpack, rollup and vite with monorepo’s struggles! Who knows knows!
  • Making a devtools extension
  • Multiple build types, umd, cjs esm

I really didn’t know any of that before starting! What a journey!

Thanks for reading! Hopefully you learned a thing or two. See you next time!

All hail async-states!

--

--