I think you really need setState to have some idea who might be interested in this state transition. One idea I had was to have an optional argument to specify the "Persistor" [sic], and then I consult the "Reader" in the constructor to build the initial (local) state, and in for own code, I ended up with something pretty much like that.
this.setState({ fields, row: 0 }, UpdateURL)
where: setState(x,y) { return y&&y(x)&&super.setState(x); }
I haven't had any need for multiple "Persistors" [sic], nor any idea what multiple "Readers" might do, but you should find it easy to add a pub/sub mechanism in there if you needed it. import ViewModel from 'statium';
import stateToUri from 'urlito';
const defaultState = {
foo: 'bar',
qux: {
time: Date.now(),
},
};
const [getStateFromUri, setStateToUri] = stateToUri(defaultState, [
'foo',
{
key: 'qux.time',
uriKey: 'time',
fromUri: time => parseInt(time, 10),
toUri: time => String(time),
},
});
const Component = () => (
<ViewModel initialState={getStateFromUri}
observeStateChange={setStateToUri}>
{/* ... */}
</ViewModel>
);How does changing the URL trigger a state change? Does the whole page need to reload?
In my system, my "Reader" knows this object so it can just call the regular setState (I also have some mount/umount logic there to avoid leaking memory). This makes back/forward transitions very snappy!
I can also use the same logic for my Server (another "Persistor" [sic]) which indeed uses POST for submitting updates, but responses come down a shared SSE stream (which I need anyway so that users can see eachothers changes in real-time).
Sorry, I should have been more forthcoming with explanations but got paged at $work. Almost nobody is familiar with Statium yet, it's very new. :) It was developed for cloud applications UI here at DataStax.
Statium implements a very simple key/value storage in a React component called ViewModel. It is using `setState()` internally, so all the usual React rendering logic applies unchanged. Each ViewModel has access to keys of its ancestors, all the way up the chain. There is no global store but a chain of stores instead, which helps to keep state local to consumer components that use it.
Urlito is just a simple library for persisting state to and from URI, currently using query params. This is intended for local component state like selected tab, sort order in a table, or a list of expanded rows in a tree grid, the sort of things that do not deserve full blown URI routing pattern matching. We still use `react-router-dom` for that.
> How does changing the URL trigger a state change? Does the whole page need to reload?
No, it's the usual React logic: when ViewModel renders it will call the function provided in `initialState` prop, which in turn will read the current state of the model from URI query string. Whenever ViewModel state changes, `observeStateChange` function is called, and updates URI to reflect the current model state.
Urlito implements the functions for reading keys/values from URI and writing them back to URI, with support for default values and key filtering.
> In my system, my "Reader" knows this object so it can just call the regular setState (I also have some mount/umount logic there to avoid leaking memory). This makes back/forward transitions very snappy!
Almost the same solution in Statium, except that we have a full blown class based React component to hold the state, and hooks are not used internally (there is a hook based consumer API). The main reason for not using hooks for us is that the values held in `useState` are hard to propagate down the component tree, and hard to test. ViewModel takes care of this easily, with each key available anywhere downstream, e.g. a Component somewhere deep can retrieve the value of the topmost ViewModel without having to access it directly. This helps mightily with testing too: just wrap your tested components with a ViewModel and pass whatever you want to it (including state changes).
No memory leak issues at all, since a ViewModel is simply a React component that outsources `this.setState` for consumer components. :)
Transitions are very snappy with Statium, too. In fact, state updates are lighting fast: value updater function will walk up the ViewModel tree, find the closest owner and set the value in it using `setState`. This will cause the owner ViewModel and its children to re-render, but the render will be automatically scoped to the least amount of components. Since the state is usually localized, the problem of updating the whole app state on a keypress does not apply by default.
> I can also use the same logic for my Server (another "Persistor" [sic]) which indeed uses POST for submitting updates, but responses come down a shared SSE stream (which I need anyway so that users can see eachothers changes in real-time).
Asynchronous logic is hard to handle in synchronous React rendering paradigm... That was the reason for me to come up with a ViewController concept (https://github.com/riptano/statium#viewcontroller), which is the other part of Statium. The idea is to write business logic in a more imperative style, statements not functions:
const loadUserPosts = async ({ $get, $set }) => {
let [user, posts, comments] = $get('user', 'posts', 'comments');
if (!posts) {
await $set({ loading: true });
posts = await loadPosts(user);
await $set({ loading: false, posts });
}
if (!comments) {
await $set({ loading: true });
comments = await loadComments(user, posts);
await $set({ loading: false, comments });
}
};
Decyphering this: $get is a function that returns ViewModel state values by keys, and $set allows updating these values. This is a matter of personal preference of course, but I think this approach makes the logic much easier to read and understand than Redux thunks or sagas.There's also the RealWorld example app I came up with for React + Statium, check it out: https://github.com/nohuhu/react-statium-realworld-example-ap.... It's not yet submitted to the official list because I'm stuck trying to come up with a sensible logo for it... :)