Skip to content

Instantly share code, notes, and snippets.

@developit
Last active May 2, 2017 14:52
Show Gist options
  • Save developit/3f619898695c0336acf35e29e7c27dca to your computer and use it in GitHub Desktop.
Save developit/3f619898695c0336acf35e29e7c27dca to your computer and use it in GitHub Desktop.

An interesting challenge for centralized state

I build distributed frontends using components (preact components, but that's not important). Components are a great unit of composition when the structure of a system cannot be statically determined.

Prerequisites

In the following examples, I'm going to pretend there exists a common definition for a <SplitPoint> component:

SplitPoint invokes an async load() function (passed as a prop), then renders result as its child.

A horribly naive implementation of this component might look like this:

class SplitPoint extends Component {
  constructor({ load }) {
    load().then( Child => this.setState({ Child }) )
  }
  render() {
    let { Child } = this.state
    let { load, ...props } = this.props
    return <Child {...props} />
  }
}

We can use this component to lazy-load (webpack-)chunked components on first render. Normally in Webpack this might look like:

<SplitPoint load={ () => import('./SomeComponent') } />

We can also emulate this without any real chunk using a function that returns a Promise resolving to a Component:

// we're emulating a chunk here: a Promise that returns a Component
const loadComponent = () => Promise.resolve(
  class SomeComponent extends Component {
    render() { return <div>whatever</div> }
  }
)

<SplitPoint load={ () => loadComponent() } />   // unnecessary? yes

The main point here is that we can asynchronously pull in new components and render them into the Virtual DOM tree, simply by wrapping them in a <SplitPoint /> and chunking the Component definition.

Here's an example of what that looks like:

const Sidebar = () => (
  <aside>
    <SplitPoint load={ () => import('./Ad') } />
    <SplitPoint load={ () => import('./Map') } />
    <SplitPoint load={ () => import('./Nearby') } />
    <SplitPoint load={ () => import('./AnotherAd') } />
  </aside>
)

... rendering that <Sidebar /> actually triggers network calls to go get the necessary components, and when they resolve they are rendered in-place.

The same technique is easily used to chunk routes/pages/whatever.

But centralized state!

Let's say we're using something like Redux for a centralized store. The components are going to pull initial state from that central store, and subscribe to to changes to update in response.

The components in our Sidebar were Ad, Map, Nearby and AnotherAd because revenue is important. Let's ignore the Ads (everyone does) and focus on Map and Nearby. Both of these Components want to use the same location value from the centralized store.

The location value isn't free, though. It's existence in that store includes a bunch of code to

  • fetch the user's location from an IP address
  • geolocate them using browser APIs
  • save location preferences to localStorage; or if signed in,
  • save an authenticated user's location preferences to a service somewhere

That's a nontrivial amount of code. If we're building a sufficiently large application, it seems like there would come a time when the functionality associated with values in the store would be worth splitting out into chunks that we could load on-demand. Or, perhaps we don't know ahead of time (during a build) if there will even be Components in this application that rely on that location value at all? Maybe their existence in the UI is determined at runtime by something like a CMS, authentication status, or flags?

The logical solution here would be to code-split the logic around that location value so that it's only downloaded and executed when the application actually needs to use it. However, how do we know that the application needs to access location?

// perhaps getters on `state`?
@connect( state => ({
  location: state.location
})
export default class Map extends Component {
  render() {
    this.props.location   // accessed synchronously
    return <div>a map</div>
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment