4 ms·
There should be a single useState hook which takes an optional `{persistor, reader}` param. Then the choice of whether to stuff this in local storage, URL, loca
by rbalicki 7y ago
There should be a single useState hook which takes an optional `{persistor, reader}` param. Then the choice of whether to stuff this in local storage, URL, local state, global (transient) state, post it to an endpoint, etc is all configurable depending on the use case.
- geocar 7y agoI've thought about something like that, but then what namespace would the keys belong to? Are they just some kind of hidden "superglobal" and if so, how do you reconcile the intention that state be limited to its component? On the other hand, you could (somehow) build unique symbols for each instance, then have the problem of who's responsibility is it to nominate friendly names for the query string (or yuck, __viewState!)? Or you could make it the persistor's [sic] responsibility; give it this, the key and the value, and let it worry about this exact same problem? Even if you choose one way, there's going to be people who want it a different way, so you've got to be right! 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.
- nohuhu 7y agoThis 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> );
- geocar 7y agoI'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 7y 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 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-app 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... :)