This is third week after I decided to ditch Redux and wrote my own state management — GState. First post, I described basic functions of the solution. Second post, I demonstrated how to apply unidirectional data flow, state declarative patterns and solve encapsulation problems.

In the post, I discuss about explicit and implicit data fetching, state anchoring pattern and server side render with GState.

Explicit Data Fetching

A very common task in front-end applications is pulling data from server side to update UI. It was easy if the data is on local. It is hard because we must handle over-fetching, under-fetching, data inconsistency, data relationships and connectivity issues. But the first question we must answer: when and where to put fetching data logic.

Model-View-Controller Pattern

Model-View-Controller could consider as most successful pattern, we still use it for data fetching on server-side. Whenever we visit a web url, the controller code will pull data from remote database or service. Then it combines the data with a certain view to render the web page.

app.get("/", function(req,res) { // server side routing controller

db.find({}).then(model => { // load model from remote database

res.render("view", model); // combine model with view to render

})

});

The same pattern could apply for data fetching on client-side too.

page("/", function(ctx){ // client side routing controller

ajaxFecthData().then(data => { //fetching data from server

ReactDOM.render(<View data={data} />, rootEl); //React render view component with data

})

})

Here is full working client-side MVC sample combine a micro client-side router page.js , gstate and axios.

I use page.js because it is easy to make analogy with expressjs code on server-side which page.js as Controller, gsate as Model and React as View.

The sample works exactly like server side MVC. Whenever we navigate to a page, controller will pull data from server and put into gstate. Then it uses React to render certain component with state. The obvious issue that it is very slow because we’re re-fetching data every time. To solve the issue, we use a method I call state anchoring.

State anchoring

GState use the pattern extensively to solve many state management problems. Basically, the method is make object reference to well known path.

const value = { a: "a" }; state.set({

well_known_path: value // reference the value to well known path

a: {

b:{

c: value // original state

}

}

}); // Next time we clear original state

state.set({ a: undefined }); // Value still survive at well_known_path

state.get("well_known_path.a") => "a" // re-link to original or link to other state

const dummy = {};

state.set({

well_know_path: dummy,

a: {

b: {

c: dummy // re-link

}

},

other: dummy // link to other

}); state.get("a.b.c.a") => "a"

state.get("other") => "a"

Update fetchStories with state anchoring

The sample load faster because only new items fetched:

State anchoring works like how ApolloJS, Relay cache GraphQL data from server but GState is unopinionated (it doesn’t need globalId ).

State anchoring in encapsulation.

State anchoring works perfectly with encapsulation.

The sample create 4 same components at different contexts and an anchor at root context. There are 2 different update function: one without state anchoring and one with state anchoring.

updateTextWithAnchor use reserved property “#” which tell gstate put the value into root context regardless current context. The method help encapsulation components link their local state with well-known-path (from root). Because all 4 components link with the same well-known-path /item, update one place will update all 4.

The pattern could apply to solve data inconsistent, communication between components and many more.

Server Side Render

GState have 2 apis to support SSR:

state.save: clone internal state into transferable value.

clone internal state into transferable value. state.load: consume previous transferable value to rebuild internal state.

Now update Hacker News sample with SSR.

Notice: I only apply SSR for route /new to compare with other routes. Reload route /new will not trigger client-side data fetching at the first time. Next navigate to route /new will.

Client side code change a little to load initial state into gstate.

Server side code will replace page with express router and browserRender with ServerRender.

Notice: in real cases, we should use universal router or wrap page.js to merge client and server routing code. So we could use same code for both.

//Something like

app.get("/new", (req, res) => {

universalRender(View, props, dataFetching);

})

Implicit Data Fetching

This is interesting one, I will research more in future. The idea is the components should be not worry about data fetching. They query data from GState as if all data stored in local. GState should detect data query and pull data from server. Whenever the data ready, components will be notified and update.

Sample for implicit data fetching here. Notice, I’m not satisfy with current implement and it will be changed in future.

In the sample, controllers are no longer explicit fetching data for view. Instead of using state.reader which add a hook into a well known state. Whenever components try to read data from the state, state.reader hooks will called.

The sample make a hook at /pages and Story components try to read data from /pages/[type]. Then the hook called and check current story’s type already loaded. If not, it will pull data from server and set the state. Story components get notified and update UI.

The pattern work same with Relay QueryRenderer or ApolloJS. It could use to fetch relationship data, synchronize with server state, or even pull data from offline storage. Components doesn’t need aware where the data come from.

Finals

So far, I think GState cover enough use-cases for an typical state management solution. I’m going release first version in next week; hope you guy interested to keep the project alive.