After lots of tweaks based on the valuable feedback from the community, SimpleR State is finally at its first official stable release v1.0.0.
Big thanks go to the great people that contributed to this effort to bring it to Production grade. I can now confidently call it the "simplest" out there. Mission accomplished.
It looks promising. I wonder how it will play with TypeScript. Would need to dig deeper. However the angle to make it simple is great. No boilerplate code, unit testing as it should have always been. Great work!
Happy to say that this also works well with TypeScript with minimal explicit types required. My main goal is to maximize the use of type inference. The documentation provides TypeScript version of each example (outside of the TL;DR cheat sheet), except in cases where TS version is the same as the JS due to type inference. Even where there is TS version, the difference from JS is minimal.
Usage feels very similar to redux when using redux toolkit, and the latter comes with a more mature ecosystem.
There was also nothing about streams and such that you would normally handle with redux-observable or redux-saga.
One of the super powers of redux+saga is being able to listen to every state update (and act according to that), this doesn't seem to be possible (unless you make a super entity I suppose?).
A quick glance at the documentation showed that you can write plug-ins to do such things as listening to all state changes (unless I misunderstood something). See the logging plugin example.
Without a Chrome dev tools extension though I don't think I could leave redux just yet.
Usually there would be a problem importing/defining things from static files like that as this would be shared within the server between instances of renderings.
The simplicity goes away when supporting SSR. You have to depend on multiple stores (one per request) being injected "from the top" (e.g via Context Provider).
SimpleR State acts as a "module-level store", one of the reasons why it's so simple. When you start doing SSR, though, as with anything else in React global state management, you would sacrifice some of that simplicity.
So to answer the question, yes SimpleR State works with SSR, but you need to take an extra step. You would need to somehow "scope" your global state inside the React component tree. The most common solution is using a Context to store your server-side entities, so that they are not "shared" among SSR requests.
SimpleR State proclaims itself as "unopinionated", so the preferred SSR solution is a decision that the developer has to make. It would be worth adding some options in the documentation, though. So thanks for bringing this up. I also received a few requests on such documentation in the previous Request For Comments during the beta run. A doc page on SSR is underway.
I've been prototyping something server-side in the style of the SQL Kappa architecture: facts on immutable logs, possibly keyed (and compactable by key), and SQL materialized views streaming the updates with Flink. And I'm in search of a React state manager in that philosophy.
Rows are sent to each client requesting a feed of them:
INSERT INTO user_orders_total_feed(user_id, data)
SELECT
uo.user_id,
SUM(o.price) total
FROM user_order uo
JOIN order o ON uo.user_id = o.user_id
JOIN user_live_subscription uls ON uo.user_id = uls.user_id
GROUP BY uo.user_id
WHERE uls.subscription_topic = 'orders_total'
But: maybe you want all the orders in the UI and do the aggregation client-side, to have all orders alongside the total. You could move the aggregation to the client side. And it would be great to have the same data language than on the server (so that business rule expression is the same).
In that case, a React Hook library like SimpleR State could aggregate in SQL the sum and update the view reactively. It would look like:
I don't think that would be easy to do; but I think there is a market for it: The business folks doing no-code+SQL client-side, and they would sometimes ask through proper channels for a query to be computed globally server-side or be brought client-side.
Although this is not provided by SimpleR State out of the box, the library makes it easy to implement a "selector" function that accepts an SQL query, and pass this selector to the entity hook. This SQL query syntax in React can well be a library on its own, I suppose.
Yes, I called it "transform function" in the documentation to reflect the more general functionality that also includes computed values, apart from selection ("picking" properties).
I think SQL on the frontend is a big opportunity. It’s a powerful declarative language that matches nicely to React.
Every component’s data requirements are essentially an SQL query, and a lot of client-state could probably be represented in further SQL queries (filtering, aggregation).
Doing joins makes things much more consistent too when the same entities are referenced in multiple places in the UI.
Just like React works declaratively by re-rendering the virtual dom, wouldn’t it be great if we just re-ran a declarative query for the data too?
Kind of like Redux (global state) I guess but a relational model version.
We tried this approach and have attempted to bend React Context to our need, but it feels like its gotten pretty hacky. Are you only using component state and Context or what is your approach?
I haven't tried this one, but for what it's worth, if you're looking for a simple state manager, I LOOOOOOOOOVE Pullstate.js. I've been using it for a reasonably complex frontend and it's just excellent, an absolute breeze to work with. For reference, I'm the type of person who appreciates the power of redux but views it as conceptually overcomplicated to the detriment of the developer experience-- I felt it was overhyped and added limited value relative to the cost of its complexity. (Not that it's that complex, but more than a state manager needs to be.)
Pullstate is extremely simple but just works perfectly.
That looks really good. I like how the hook is built into the store API.
My problem with a lot of these simple state managers is they often just stop short of handling asynchronous state. I think it should pretty much be mandatory to handle pending and error states on promises for state manager nowadays. Pullstate's Async Actions look pretty good for that.
I've been using Effector a lot recently because it has just a bit more power in managing the interactions between stores and events but if I needed something more straightforward Pullstate would be good. Thanks for the tip!
If you like this API but are already using Redux (or still want to use Redux), I've found Rematch[1] to be a very productive wrapper around redux that has a DX almost identical to this library.
I'm an avid user of mobx and yes, this is similar, but no where near as powerful, and much less magical. For example, in no particular order:
* mobx is smart enough to know when only one property of a complex object has changed and that that component depends only on that property. So you can have complex state objects, and if your component only uses property X of said object, it only re-renders if X changes but not Y.
* mobx allows you to compose observables - any observable can reference the value of any other observable and if that second observable changes, the listeners for the first observable will re-render, etc. This means state can depend on other state, and everything updates correctly. For example, if you have a store that fetches profile data for the auth user, and the auth user id changes due to login or logout, the profile data store can trigger a refetch, all this happening outside the react component tree, then finally the correct components re-render.
* mobx works on it own and is agnostic to framework (react/vue/etc) and is integrated via binding libraries for each framework.
* mobx provides quite a few different kinds of observables with different behaviors (shallow maps, lists, etc)
* mobx allows you to declare arbitrary reactions that run automatically when state changes, whereas this library here really just re-renders components when state changes.
* With mobx you don't even have to say which things in your component use observables - you just wrap your component in observer(), and any time it references an observable, mobx tracks it and will re-render the component if the observable changes. Whereas here you need explicitly hooks for each state atom.
There are many more differences, in every case mobx does more. So mobx is a far more complex and powerful system, whereas this is a simpler system that extends useState() hooks with objects that live outside the component. Which is not a value judgement, use the simplest tool that meets your needs.
Note that our official Redux Toolkit package specifically resolves most of the concerns people have expressed over the years about "Redux boilerplate", including drastically simplifying writing reducers and actions and including support for thunks out of the box. RTK is now the standard approach for writing Redux logic:
I found effector[1] after I started looking around for a state manager which:
1. Either supported multiple stores or provides a way to subscribe to individual branches (as opposed to the entire store).
2. Had an action/event abstraction similar to redux where multiple branches (or multiple stores) could subscribe to the same action and update independently (and without knowledge of invoker).
I have been using it for some time and have had a good experience.
The core is framework agnostic (with additional integrations for react & vue).
I started used it with Svelte and its API matches the expectations of Svelte stores so you can use it without needing any additional integration, which was cool. Later I used it with lit-element through a 5 line custom integration and that worked pretty well too.
Fragmentation comes with open source. State management is fragmented on the backend as well: PostgreSQL, MongoDB, Kafka, Redis, Dynamo, Influx, etc...it's just that state management fundamentally has different trade-offs based on how you structure it (and may involve 1 or several of these).
If your app is simple, you don't really need a separate state-management solution (just use the built-in React useState and useReducer hooks). If it's complex, you have a choice to make, but that's a fundamental issue with apps at scale and you have to make those choices top to bottom.
I feel like we are still waiting for someone to create a nice multi-paradigm database blending everything we’ve learned in the past 50 years, that can also run in the browser.
I see all state as a stream of events building a graph, then we run queries on that graph. Then it’s all about optimizing the queries which we do.
We’ve kind of shoehorned our use cases into whatever dbs we had available and have probably been overwhelmed by the speed we needed to scale our cloud storage resulting in sub-optimal solutions. Or maybe it’s just wishful thinking that there will exist a db to do it all.
The choice of `set` taking a function is a questionable one. I know this is fairly common for JS libraries but IMHO it should only be used when you know that the value can't naturally be a function. Otherwise it is a footgun.
let someval = () => 1234;
state.set(someval) // Oops.
You need to remember to write the following if the value might be a function.
state.set(() => someval);
I would have just made this a separate function and call it `.update()`.
39 comments
[ 3.2 ms ] story [ 72.3 ms ] threadBig thanks go to the great people that contributed to this effort to bring it to Production grade. I can now confidently call it the "simplest" out there. Mission accomplished.
[1] https://github.com/pmndrs/jotai
There was also nothing about streams and such that you would normally handle with redux-observable or redux-saga.
One of the super powers of redux+saga is being able to listen to every state update (and act according to that), this doesn't seem to be possible (unless you make a super entity I suppose?).
Without a Chrome dev tools extension though I don't think I could leave redux just yet.
Usually there would be a problem importing/defining things from static files like that as this would be shared within the server between instances of renderings.
This is my take on the problem -> https://github.com/Munawwar/react-global-states/tree/ssr
So to answer the question, yes SimpleR State works with SSR, but you need to take an extra step. You would need to somehow "scope" your global state inside the React component tree. The most common solution is using a Context to store your server-side entities, so that they are not "shared" among SSR requests.
SimpleR State proclaims itself as "unopinionated", so the preferred SSR solution is a decision that the developer has to make. It would be worth adding some options in the documentation, though. So thanks for bringing this up. I also received a few requests on such documentation in the previous Request For Comments during the beta run. A doc page on SSR is underway.
I've been prototyping something server-side in the style of the SQL Kappa architecture: facts on immutable logs, possibly keyed (and compactable by key), and SQL materialized views streaming the updates with Flink. And I'm in search of a React state manager in that philosophy.
Rows are sent to each client requesting a feed of them:
But: maybe you want all the orders in the UI and do the aggregation client-side, to have all orders alongside the total. You could move the aggregation to the client side. And it would be great to have the same data language than on the server (so that business rule expression is the same).In that case, a React Hook library like SimpleR State could aggregate in SQL the sum and update the view reactively. It would look like:
I don't think that would be easy to do; but I think there is a market for it: The business folks doing no-code+SQL client-side, and they would sometimes ask through proper channels for a query to be computed globally server-side or be brought client-side.I do agree too that this definitely should be a library on its own.
Anyway thanks for your work; I'll definitely check SimpleR in my project.
[1] https://github.com/arnelenero/simpler-state/blob/f6336817c9e...
Thanks for your encouraging feedback.
Every component’s data requirements are essentially an SQL query, and a lot of client-state could probably be represented in further SQL queries (filtering, aggregation).
Doing joins makes things much more consistent too when the same entities are referenced in multiple places in the UI.
Just like React works declaratively by re-rendering the virtual dom, wouldn’t it be great if we just re-ran a declarative query for the data too?
Kind of like Redux (global state) I guess but a relational model version.
It isn’t native to react, but it appears it will be in a similar form in the future.
It has been a joy to use so far. Having server side state working so seamlessly with the client is a great feature for us.
Pullstate is extremely simple but just works perfectly.
My problem with a lot of these simple state managers is they often just stop short of handling asynchronous state. I think it should pretty much be mandatory to handle pending and error states on promises for state manager nowadays. Pullstate's Async Actions look pretty good for that.
I've been using Effector a lot recently because it has just a bit more power in managing the interactions between stores and events but if I needed something more straightforward Pullstate would be good. Thanks for the tip!
[1] https://rematchjs.org/
I've tried both of these and actually not fully able to decide which one to pick. I'm inclined towards kea.js
* mobx is smart enough to know when only one property of a complex object has changed and that that component depends only on that property. So you can have complex state objects, and if your component only uses property X of said object, it only re-renders if X changes but not Y.
* mobx allows you to compose observables - any observable can reference the value of any other observable and if that second observable changes, the listeners for the first observable will re-render, etc. This means state can depend on other state, and everything updates correctly. For example, if you have a store that fetches profile data for the auth user, and the auth user id changes due to login or logout, the profile data store can trigger a refetch, all this happening outside the react component tree, then finally the correct components re-render.
* mobx works on it own and is agnostic to framework (react/vue/etc) and is integrated via binding libraries for each framework.
* mobx provides quite a few different kinds of observables with different behaviors (shallow maps, lists, etc)
* mobx allows you to declare arbitrary reactions that run automatically when state changes, whereas this library here really just re-renders components when state changes.
* With mobx you don't even have to say which things in your component use observables - you just wrap your component in observer(), and any time it references an observable, mobx tracks it and will re-render the component if the observable changes. Whereas here you need explicitly hooks for each state atom.
There are many more differences, in every case mobx does more. So mobx is a far more complex and powerful system, whereas this is a simpler system that extends useState() hooks with objects that live outside the component. Which is not a value judgement, use the simplest tool that meets your needs.
Well done for getting some community traction!
- https://redux-toolkit.js.org
- https://redux.js.org/tutorials/fundamentals/part-8-modern-re...
1. Either supported multiple stores or provides a way to subscribe to individual branches (as opposed to the entire store).
2. Had an action/event abstraction similar to redux where multiple branches (or multiple stores) could subscribe to the same action and update independently (and without knowledge of invoker).
I have been using it for some time and have had a good experience.
The core is framework agnostic (with additional integrations for react & vue).
I started used it with Svelte and its API matches the expectations of Svelte stores so you can use it without needing any additional integration, which was cool. Later I used it with lit-element through a 5 line custom integration and that worked pretty well too.
[1] https://github.com/effector/effector
If your app is simple, you don't really need a separate state-management solution (just use the built-in React useState and useReducer hooks). If it's complex, you have a choice to make, but that's a fundamental issue with apps at scale and you have to make those choices top to bottom.
I see all state as a stream of events building a graph, then we run queries on that graph. Then it’s all about optimizing the queries which we do.
We’ve kind of shoehorned our use cases into whatever dbs we had available and have probably been overwhelmed by the speed we needed to scale our cloud storage resulting in sub-optimal solutions. Or maybe it’s just wishful thinking that there will exist a db to do it all.
Literally as simple as it gets.