top | item 22446312

(no title)

nohuhu | 6 years ago

This is what Statium and Urlito are for:

    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>
    );

discuss

order

geocar|6 years ago

I'm not familiar with either of them, but that seems like a lot more code!

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).

nohuhu|6 years ago

> I'm not familiar with either of them, but that seems like a lot more code!

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... :)