Last active
December 11, 2017 18:36
-
-
Save ryedin/6efcc8c0883bb2b1baf4d9a2f19ef105 to your computer and use it in GitHub Desktop.
Example of single "app" constructed from multiple `Html.React` calls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using React.Web.Mvc | |
@using SomeFramework.Mvc.AmazingHTMLHelpers | |
@model MyNamespace.Partials.SomeSpecialViewModel | |
<!-- since this partial relies on magic "SomeFramework" mechanisms to get its data, | |
the path of least resistance to get all of this working is to just use the Razor | |
engine as "SomeFramework" expects it to work, call its special Html helper extension, | |
and plug the resulting data into our `RegisterViewData` component. This means that | |
1. future calls to Html.React (within THIS REQUEST) will have this data available and our SSR will be accurate | |
2. this data will get nicely hydrated on the client side to setup the initial view state | |
--> | |
@Html.React("Components.RegisterViewData", new { | |
viewName = "SomeComponentOneViewData", | |
data = Html.WizzBangHolySmokesAmazingHtmlHelperMethodThatReliesOnASpecificViewModelInterface(Model) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { Provider, observer } from 'mobx-react' | |
import { viewDataStore } from './stores' | |
import { SomeComponentOne } from './SomeComponentOne' | |
@observer | |
class App extends Component { | |
render() { | |
return ( | |
<Provider viewDataStore={viewDataStore}> | |
//The SomeComponentOne component will render, grab the store from context (via the injection semantics) | |
//and potentially see no data the first render cycle. This is normally (i.e. on the client) OK, because | |
//our store is "reactive", and will auto-re-render any components that care about its data. This of course | |
//falls down on the server because the context across Html.React calls is destroyed. There is no concept | |
//of a "re-render", either, because the components are not considered "live"; they are simply mounted and rendered | |
//out as a static string with no real notion of being inside a larger app context, with potentially injected | |
//data. | |
//Also, it helps if you imagine that SomeComponentOne is comprised of many levels of nested | |
//components which would make it really unwieldy to force all props to be defined and pushed | |
//down from this level (which would mean you'd need to expose every piece | |
//of data that you want to be available for your entire app and all of its children components | |
//if you want to achieve accurate SSR and gain SEO benefits, etc...) | |
<SomeComponentOne /> | |
</Provider> | |
) | |
} | |
} | |
export { App } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { viewDataStore } from './stores' | |
class RegisterViewData extends Component { | |
/** | |
* when this component is mounting, grab the view data props and add them | |
* to the ViewDataStore singleton instance. once this happens, other components | |
* will be able to respond as needed to render the data. | |
*/ | |
componentWillMount() { | |
const { viewName, data } = this.props | |
viewDataStore.setViewData(viewName, data) | |
} | |
render() { | |
//this is a non-visual component so simply render nothing | |
return null | |
} | |
} | |
export { RegisterViewData } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react' | |
import { inject, observer } form 'mobx-react' | |
/** | |
* Again, it helps to imagine that this component is actually composed of many levels | |
* of nested children, or that it is actually itself nested deeply within a larger | |
* component hierarchy. One in which the data that it needs is really only needed to be | |
* known at the top-level and this level (and having to push the data down all the levels | |
* of children is basically untenable), AND the data may or may not be present the first time this component | |
* is mounted and rendered. On the client, this is just fine (some stores might have the data right away | |
* and some might require async fetching to get populated). On the server, we'd _like_ for this to be fine as | |
* well, because it would greatly increase the flexibility of how we compose the higher level application. | |
* As of now, we can only approximate this by turning off engine pooling and making sure our Html.React calls | |
* happen in the right order (ordinality of these calls also wouldn't make a difference, if we were dealing in | |
* "live" components that have a chance to re-render before the final HTML string is created) | |
*/ | |
@inject('viewDataStore') | |
@observer | |
class SomeComponentOne extends Component { | |
render() { | |
const { viewDataStore } = this.props | |
const myViewData = viewDataStore.views.get('SomeComponentOneViewData') | |
if (myViewData) { | |
return ( | |
<div> | |
some data from the store: {myViewData.aPieceOfDataLivesHere} | |
</div> | |
) | |
} else { | |
//data isn't here yet, no worries, just render nothing. | |
//NOTE: if a re-render never happens before the final string building happens, | |
//our SSR rendering is broken (we lose SEO and other benefits for this view). | |
return null | |
} | |
} | |
} | |
export { SomeComponentOne } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@using React.Web.Mvc | |
@model MyNamepace.Pages.SomePageModel | |
<!-- blah blah blah, meta, head, bundles, etc... --> | |
<!-- ok here's where it gets interesting. We are using a CMS Framework, | |
we'll call it "SomeFramework", that has its own Html helper extension methods, | |
and relies heavily on the Razor engine to recursively expand html fragments and | |
other data. Those helper methods require to be run within the context of a view | |
or partial whose model may vary wildly from the model I am in right now. | |
No worries, we're in a single request, and that means a single js execution context, | |
which means those other partials can simply make calls to our `RegisterViewData` | |
react component, and then our top-level `App` component can still nicely be composed | |
without having to manually maintain a huge list of props and push them down the chain. | |
The "maintain" word is key here, as if that _were_ the case, it would be a nightmare | |
any time we wanted to re-structure pieces of our application --> | |
<!-- use normal partial semantics to get view data render calls without trying to go against | |
the grain of "SomeFramework" --> | |
@Html.Partial("Some/Path/To/_MyPartial") | |
<!-- finally, render our page-level react "App" --> | |
@Html.React("Components.App", new { | |
someTopLevelPropThatOnlyAppCaresAbout = "hello, world" | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { action, ObservableMap } from 'mobx' | |
class ViewDataStore { | |
views = new ObservableMap() | |
@action setViewData(viewName, data) { | |
this.views.set(viewName, data) | |
} | |
} | |
//right now, this store is going to be completely empty, which is _fine_ normally | |
//(client side, thanks to mobx, as data arrives any components that care will be | |
// automatically re-rendered... yay!) | |
const viewDataStore = new ViewDataStore() | |
//export the singleton instance because we want multiple calls to `Html.React` to share this | |
//store so that the mobx reactions can take place as expected, _even_ if our "app" is comprised | |
//of multiple mounted react "roots". | |
//NOTE: this is where the default engine pooling semantics fall down for us. | |
//Also, since there is not "live"ness to the components on the server, we need to make sure we | |
//render out all calls to the "RegisterViewData" component _before_ the call to the app-level component | |
//that wants to consume this data. It sort of defeats at least one reason for using reactive stores and | |
//dependency injection, but that's what we have to do to make this work for now. | |
export { viewDataStore } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment