Skip to content

Instantly share code, notes, and snippets.

@faceyspacey
Last active April 8, 2019 10:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save faceyspacey/7142893f50f032bb9c25e025f7e34beb to your computer and use it in GitHub Desktop.
Save faceyspacey/7142893f50f032bb9c25e025f7e34beb to your computer and use it in GitHub Desktop.
Respond Modular Components
export default createApp({
components: {
App: (props, { location }) => {
const Component = location.components.list.ComponentWithHoistedDataDeps
return Component ? <Component /> : <Spinner />
},
},
routes: {
LIST: {
path: '/list/:category',
load: async (request, action) => {
const module = await import('./component-with-hoisted-data-deps')
return module.default({
callbacks: {
beforeEnter: 'beforeEnter',
thunk: 'thunk',
onEnter: 'onEnter'
},
deps: {
category: action.params.category, // map params to deps!!
}
})
}
}
}
})
// component-with-hoisted-data-dep.js:
export default ({
deps,
callbacks: {
beforeEnter = 'beforeEnter',
thunk = 'thunk',
onEnter = 'oneEnter'
}
}) => createModule({
routes: {
entry: {
[beforeEnter]: [verifyAccess, saveScroll],
[thunk]: ({ api }) => api.get(`items/${deps.category}`),
[onEnter]: restoreScroll,
}
},
components: {
ComponentWithHoistedDataDeps: (props, state) => (
<ul>
{state.items.map(item => <li>{item.title}</li><)}
</ul>
)
},
reducers: {
items: (state, actions) => action.payload
}
})
@faceyspacey
Copy link
Author

faceyspacey commented Apr 8, 2019

What has to be understood about Respond is that ComponentWithHoistedDataDeps will only be available if its thunk has run, thereby providing its data. Even if you navigate to another route, and the component is still showing, it will continue to get the same data from state.

The thunk is guaranteed to have run because its merged into the route that's already executing with load in the parent module. In other words, because the plane is being built while it's flying we are able to guarantee that both the component and thunk (and reducers) are all available when first rendered.

After that, you could perhaps use the component ComponentWithHoistedDataDeps on another route, i.e. here:

App: (props, { location }) => {
      const Component = location.components.list.ComponentWithHoistedDataDeps
      return Component ? <Component /> : <Spinner />
    },

but it will still run because the data it depends on is still stored in its namespaced slot of state.

You could even change its state like this:

createModule({
  routes: {
    entry: {
      [beforeEnter]: [verifyAccess, saveScroll],
      [thunk]: ({ api }) => api.get(`items/${deps.category}`),
      [onEnter]: restoreScroll,
    },
    refresh: ({ api }) => api.get(`items/${deps.category}`)
  },
  components: {
    ComponentWithHoistedDataDeps: (props, state, actions) => (
      <ul>
        <Button onClick={actions.refresh} />
        {state.items.map(item => <li>{item.title}</li><)}
      </ul>
    )
  },
  reducers: {
    items: (state, actions) => action.payload
  }
})

You can add additional routes, and event handlers and do all sorts of things. What's important is the initial route using the special name entry provides the initial required data.

@faceyspacey
Copy link
Author

Lastly, for this to work, the implementation must allow for multiple such components included in a single route, and their callbacks are merged into the array form. For example, we already support:

route.thunk: [callback1, callback2]

They don't currently in parallel. They are sequential (and can be async). So we need to implement a special case where when module components are combined into the same route, they are run in parallel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment