Comments (157)
Yes, absolutely. We have plans that will probably happen this week.
Sent from my iPhone
On Jun 29, 2014, at 12:57 PM, Simen Brekken [email protected] wrote:
Route.spec.js contains a commented out test for server rendering but it seems, server rendering would at least require exposing findMatches in the main module.
Any plans for reintroducing server rendering support?
—
Reply to this email directly or view it on GitHub.
from react-router.
There is a bunch of discussion in #38 that is relevant to server-side rendering. Let's continue that discussion here.
from react-router.
Looking forward to this. Started digging through old issues and the code, but not sure where everyone is planning to start. Willing to help in any way and usually on IRC 👍
from react-router.
It throws an error in Node during initialization:
/Volumes/Work/new-project/node_modules/react-nested-router/modules/stores/URLStore.js:6
hash: (window.addEventListener) ? 'hashchange' : 'onhashchange',
^
ReferenceError: window is not defined
at Object.<anonymous> (/Volumes/Work/new-project/node_modules/react-nested-router/modules/stores/URLStore.js:6:10)
It's predictible, because there is no global window
there.
@rpflorence, could you please inform about the status of server-side routing support?
I think, that your routing lib is the most convenient and I want to build my "isomorphic" application using it, but unsupported server-side routing stops me...
from react-router.
The major blocker for server-side rendering at this point is coming up with a sane strategy for fetching data that works on both the client and the server.
Requirements are:
- It must be asynchronous
- It must support rendering the view before the data has arrived, for components that want to display "loading..." UI on the client
- It should also support synchronously passing in data, for testing
Prior art/ideas:
- In #80 @ericflo suggested we take a look at http://andreypopp.github.io/rrouter/data-fetching.html
- https://github.com/andreypopp/react-async
from react-router.
@mjackson ugh, react-async is a pile, don't make people use threads to fetch data.
from react-router.
@phated the fibers stuff is stupid, agreed. But I really like the simplicity of getInitialStateAsync
. You don't need threads to pull it off.
from react-router.
@mjackson I think the crux of async server rendering is that React.renderComponentToString is a sync operation so async things can't happen inside it, leading him to use threads. I think fetching async and passing the data into the component as initial props (sync) is the only way to do server rendering.
from react-router.
@phated But he has ReactAsync.renderComponentToStringWithAsyncState
which takes a callback, so fibers shouldn't be needed.
Right now I'm thinking:
Route#getInitialAsyncState
for components to fetch async state. If routes declare this they are asynchronous. If not, they're sync.this.props.initialAsyncState
as a way for test code to manually pass in async state.Router.renderComponentToString
Which is basically an async-aware version ofReact.renderComponentToString
.- Possibly
Route#componentWillReceiveAsyncState
as a lifecycle hook for components that need to know when they're about to receive some state fromgetInitialAsyncState
. This would only be needed if a component needs tosetState
in response to some asynchronous state. Not sure if this ever is a good idea, but seems like it could be useful.
If components need to display "loading..." UI, they can check for this.props.initialAsyncState
or just check for this.state.someAsyncProp
directly.
from react-router.
Sounds like a good plan. I think I actually have a place in current code where I would need the Route#componentWillReceiveAsyncState
lifecycle method.
from react-router.
@rpflorence @ericflo @petehunt Would love to get your thoughts here.
from react-router.
@mjackson yeah, that's pretty much exactly what I'm thinking too, minus the lifecycle hook, though that is compelling. I think we should do everything but that, start using it, and then see if we ever think "shoot, that lifecycle hook sure would be nice right now!"
from react-router.
@phated can you explain how you imagine using componentWillReceiveAsyncState?
from react-router.
This all sounds great to me! Agree with @rpflorence that the lifecycle hook seems like an optional extra. Just to be clear, on the client side there'd be no need to switch the renderComponent call from React to Router, correct?
Also, personally I'd name it something other than renderComponentToString if it acts different from React's method of the same name, and I'd remove the word "initial" from getInitialAsyncState, but this bikeshed is yours to paint :)
from react-router.
@ericflo Yes. You'll still use React.renderComponent
on the client. The async-aware equivalent is just for server-side.
from react-router.
Probably should gist or PR this but I want to see if I am even on the same track as everyone's thinking
var App = React.createClass({
statics: {
getInitialAsyncState: function(cb){
setTimeout(function(){
cb(null, {
initial: 'state'
});
}, 100);
}
}
});
function renderComponentToStringAsync(component, cb){
var done = function(err, state){
if(err){
cb(err);
}
component.setState(state, function(){
var html = React.renderComponentToString(component);
cb(null, html);
});
};
component.getInitialAsyncState(done)
}
from react-router.
I think I'd vote for promises over callbacks if we can.
from react-router.
@spicyj I would too, but usually there's more push back the other way, concept remains the same. I am unsure if I captured the thought behind getInitialAsyncState
.
from react-router.
@phated yeah, that's the basic idea. I think getInitialAsyncState
should be an instance method, similar to getInitialState
but async. Is there a particular reason you made it a static method?
Also, I think React will complain if we try and call component.getInitialStateAsync
directly on a descriptor.
from react-router.
getInitialAsyncState()
needs to be a function of the route IMO, not of the props. Otherwise you need to actually render in order to know what to fetch which won't work in a sync environment.
from react-router.
@mjackson I was confused about statics, descriptors, etc. I agree with @petehunt that it needs to be on a route, which would be a prop right?
from react-router.
you need to actually render in order to know what to fetch
Why is that a problem?
from react-router.
Because you may need to fetch something to know what to render, then you may need to fetch again to render again, etc etc
from react-router.
This is a fiddle I made a while ago which somewhat mirrors our internal stuff: http://jsfiddle.net/R88XR/
The gist of the idea is that data fetching may only be a function of the route so that data fetching is batched together and render is batched together (synchronously). So there's a static fetchData(route) -> Promise<data>
method.
The data you fetch is accessed at this.state.data
. If you render on the server, the data is fetched before rendering and passed to the component via an initialData
prop (which is copied to this.state.data
). If there is no initialData
, the component itself fetches on mount.
from react-router.
@petehunt I see. Thanks for sharing the example.
This would be so much easier if React's getInitialState
and renderComponentToString
were both async..
from react-router.
The approach in that fiddle works well for that example, but how does it scale to many nested components? Do you have to pass in all data from the top?
from react-router.
So the way I'd do it is have this mixin on each handler (which I'd rename to "entry point" if I had my way, btw) and return promises instead of callbacks. Run all the promises in parallel and pass their results back in as initialData
props.
If a child component needs to fetch data, it can provide its own fetchData()
method and its owner can pass it an initialData
prop.
from react-router.
side note: props.activeRoute
and handler
are two things I'm comfortable
bikeshedding some more.
On Thu, Jul 17, 2014 at 1:34 PM, Pete Hunt [email protected] wrote:
So the way I'd do it is have this mixin on each handler (which I'd rename
to "entry point" if I had my way, btw) and return promises instead of
callbacks. Run all the promises in parallel and pass their results back in
as initialData props.If a child component needs to fetch data, it can provide its own
fetchData() method and its owner can pass it an initialData prop.—
Reply to this email directly or view it on GitHub
#57 (comment)
.
from react-router.
what about this?
var Course = React.createClass({
mixins: [DataFetcher],
statics: {
fetchData: function(params, query) {
return course.find(params.id); // a promise
}
},
render: function() {
if (!this.state.data) {
// in the client, this will get a chance to render
return <div>Loading...</div>;
}
// on the server, we would wait for the promises
// to all resolve and set the data
var course = this.state.data;
return (
<div>
{course.name}
</div>
);
}
});
var routes = (
<Route handler={App}>
<Route name="course" handler={Course}/>
</Route>
);
// server
// give it the routes, the current route, and the current url
Router.renderRoutesToString(routes, url).then(function(markup) {
res.send(markup);
});
// client
React.renderComponent(routes, document.body);
from react-router.
@rpflorence Two pieces you're missing:
- A
componentDidMount
that callsthis.type.fetchData
whenthis.state.data == null
, so the client knows how to get data. - Some way to pass the current path to the top-level route.
from react-router.
the mixin would do the first, and I just updated the code for the second as you were typing your message :)
from react-router.
updated the code once more to just pass the url instead of route name and params.
from react-router.
It might be valuable to have some way to avoid re-fetching data when changing the params or query string in a way that doesn't affect the actual fetch request. One way to do this would be to make the API more like the following:
statics: {
getFetchParams: function(params, query) {
return {id: params.courseId};
},
fetchData: function(fetchParams) {
return course.find(fetchParams.id); // a promise
}
},
and then the router could compare the old and new fetch params with _.isEqual
or similar when the route is updated and skip the fetch if the fetch params haven't changed. Slightly more typing in the simplest cases though.
from react-router.
In my prototype (that I lost...) fetchData()
returned a set of URLs that represented fetches (i.e. {'http://mything.com/users/1': true}). Made it easy to decide when you needed to fetch new data, and also made it easy to dedupe fetches from multiple components.
from react-router.
@petehunt How does that URL get turned into actual data?
One more thing: you could also make the mixin syntax like this instead of defining magic static props on the component class:
mixins: [DataFetcher({
getFetchParams: function(params, query) {
return {id: params.courseId};
},
fetchData: function(fetchParams) {
return course.find(fetchParams.id); // a promise
}
}],
from react-router.
@spicyj You can inject fetching plugins:
ReactNestedRouter.injection.injectFetchingPlugin(new BackboneFetchingPlugin('Users', UsersCollection));
ReactNestedRouter.injection.injectFetchingPlugin(new FirebaseFetchingPlugin());
// ...
statics: {
fetchData: function(routeParams) {
return merge({
'backbone://Users/' + routeParams.userID: true,
'firebase://myfirebase/key': true
}, SomeChildClass.fetchData(routeParams));
}
// You can look up the response in this.state.data['backbone://Users/' + this.props.routeParams.userID] etc
from react-router.
@petehunt you're losing me...
from react-router.
One more thing: you could also make the mixin syntax like this instead of defining magic static props on the component class:
I wouldn't call a hook "magic" just because it lives on statics and isn't part of react's built in "magic" hooks :P
from react-router.
I wouldn't call a hook "magic" just because it lives on statics and isn't part of react's built in "magic" hooks :P
Yep. Your original fetchData proposal is pretty clean but it gets messier if you add more functions like I did. :) Plus I like making DataFetcher
a mixin factory like that because it makes it obvious that it's tied to the data-fetching functions. It also makes it harder to accidentally add one piece without the other.
from react-router.
I think what @petehunt is suggesting is that you could use different data "fetchers" (I would prefer "provider") that know about how to fetch data from different sources. Then, you'd use keys so that anybody else who needs the same data knows it's already been fetched. Kind of like how Ember hangs on to the model
in parent routes when switching to a different sub-route.
from react-router.
Basically whenever the route changes, you can very inexpensively diff the new URLs with the URLs you already have to detect what you actually need to fetch. Unpacking it is a little cumbersome but I think we could put some good sugar on this.
from react-router.
@spicyj This is one time when I really wish React didn't complain about merging props with the same name. We could provide a default impl of getFetchParams
that just returns params
and let "sub" classes override it.
from react-router.
@mjackson you could name the default impl _getFetchParams()
and have it delegate to a getFetchParams()
if it exists...
from react-router.
@mjackson Yet another thing that's magically solved if you use my mixin factory idea. :)
from react-router.
I really like the mixin factory idea, fwiw
from react-router.
@petehunt Ah, yeah. Good idea :) I really like the idea of being able to mix data from several different sources easily.
@spicyj agreed :) it's a good idea.
from react-router.
Is it valuable to be able to do two fetches in parallel where the server waits for both but they fill in as they load on the client?
from react-router.
@spicyj I think so. The code that is executing the fetches should call setState
as soon as it can and merge the new data.
from react-router.
@spicyj I think the more interesting problem is data we want to only fetch on the client but not on the server. Maybe we can just put conditionals in fetchData()
?
from react-router.
@mjackson I thought the promise returned by fetchData
turns into this.state.data
when it resolves?
@petehunt Or perhaps a clientOnly: true
in the fetch config.
from react-router.
FWIW, I like the idea of returning a "fetching descriptor" rather than a promise since it's easy to diff and dedupe a descriptor.
from react-router.
Seems like this conversation is heading toward the router being an intelligent data layer.
Forgive me if I'm being naive, but doesn't the router have to simply supply an asynchronous hook + data property when it resolves and then you can implement all of these other great ideas in the hook yourself?
from react-router.
@rpflorence +1
from react-router.
Decided to take a stab at a basic DataProvider
. Is there anything that y'all would like to do that you couldn't easily build on top of that?
from react-router.
Very excited about this, definitely going to try it out when Router.renderToString
lands. At first I was wondering why we need to worry about loading data, but after trying some server-side techniques I get it.
from react-router.
Now that activeRoute is a function, is it possible to determine what components will be rendered without actually rendering? I'm don't think it is.
from react-router.
@spicyj We don't know all the components, but we should still be able to know the route handlers. After we call dispatch()
we know which handlers are going to be active next time we render()
. Since the data fetching function is a static method of the route handler, I believe that's all we need to know.
from react-router.
I see, I guess you can't access the props passed from your parent in the data-fetching code. Makes sense.
from react-router.
Yet another feature I would like to have here is handling of 404 pages server side.
What I mean here is if browser requests for page /user/:id/profile
and there is no such user, server should render page component to string and response with 404
HTTP code. How can we handle this case?
Another unclear thing is redirects in transition hooks on server side: it should just response with 302
.
from react-router.
Hopefully transition hooks will just work. renderToString
should wait for those also.
I think 404s are weird for UI. If you didn't get a user, tell the user you didn't get a user, there's still UI you can render and give the user an experience in your render method.
But anyway, seems like this should be fine:
React.createClass({
statics: {
fetchData: function(params) {
return getUser(params.userId);
}
}
});
// on the server
app.get('*', function(req, res) {
Router.renderRoutesToString(routes, req.url).then(sendResponse, function(rejectReason) {
if (reasonIs404(rejectReason)) {
send404(res);
}
else if (reasonIs500(rejectReason)) {
send500(res);
}
else if (/* etc... */) {}
});
});
If any of your promises reject, you'll get the reason, and then you can branch or do whatever you'd like.
from react-router.
404s are needed for SEO.
Ok, I've got the case with rejected data, but there is another case, when no route matched the url. Is there any way the router can tell me, that he have just rendered 404 page?
And I haven't got your answer about transitions. I'll try to explain, what I mean: say, you have the route /admin
and only admins can get there. Our user is not admin and he enters this url in browser.
What should happen? He should be redirected to /login
and see the login form rendered.
What do we have in our case? He will see the login form rendered, but the browser will still show /admin
url in the location bar.
Or do I miss something?
from react-router.
@th0r Why don't you use transition.redirect('/login')
in your willTransitionTo
hook? That way the user will see /login
in the URL.
from react-router.
@mjackson, are you talking about serverside? What does transition.redirect('/login')
do on the server? How can it change user's browser location without sending 302
HTTP code back to the user?
from react-router.
I like the API discussed here, but don't really care/need about the async or 404 stuff. 👍 to getting a simple implementation out and building upon that for more difficult use cases.
from react-router.
@th0r Ideally, yes. We would be able to use the same API on the server as we do on the client, including things like transition.redirect
. Ultimately it will be the responsibility of the server to send the 302.
from react-router.
Alright, I've had a few conversations with @spicyj and @jaredly, and others about this and agree that we should provide a way to allow people to define multiple promises that resolve to different state on the component, not just one big hammer on this.state.data
. You want to render any UI you have data for as soon as you have it!
@jaredly and I just rapped out some API here, and I really like it. Please shoot holes through it.
var User = React.createClass({
statics: {
dataProviders: {
// these keys become state, so `this.state.user`
user: function(params) {
return getUser(params.userId).then(null, function(reason) {
// could branch here for server rendering and send 404 if you want
return { error: true, reason: reason };
});
},
activity: function(params) {
return getActivityForUser(params.userId);
}
}
},
componentDidReceiveData: function(key, data) {
// key is 'users' or 'activity'
// a chance to set up polling, retry a failed request, etc.
},
render: function() {
// router exposes the promises to the user for inspection, progress, etc.
var user = this.state.dataProvider.user.isResolved() ?
<LoadingUser/> :
<User user={this.state.user}/>;
// and for the 95% case, just check if you have data
var activity = this.state.activity ?
<LoadingActivy/> :
<Activity activity={this.state.activity}/>;
return (
<div>
{user}
{activity}
</div>
);
}
});
I really like that the user is required to define the key where the data is going to be set, I like the dataProviders
name, its very clear, and doesn't imply "fetching" or anything either, because sometimes you will synchronously return stuff. I just really like it all around, so you're going to hurt my feelings when you tell me its dumb.
from react-router.
NB: the lack of a mixin. Given that the Router will already need some omniscience about data loading in order for renderRoutesToString
to work, it seemed better not to make it seem independent through a mixin.
from react-router.
We probably still need a mixin to make this all happen, I'd like to just have a RouteHandler
that does
from react-router.
That does this and other stuff in the future that make route handlers special.
(Sorry for closing, typing in my phone and missed the textbox with my finger)
from react-router.
I still think we could do it with just one async hook function and regular old state variables.
var User = React.createClass({
statics: {
// Since people want to setState as data is received, we
// provide a setState function that works like the setState
// they already know and love.
fetchState: function (params, query, setState) {
// If you don't need to do anything async, just update
// the state immediately and you're done.
setState({
user: UserStore.getUserByID(params.userID)
});
// Or, ignore the setState argument entirely and return a
// hash with keys named after the state variables you want
// to set. The values may be immediate values or promises.
return {
user: getUserByID(params.userID) // may be a promise
};
// Something more complex...
var streamData = '';
return {
user: getUser(params.userID).then(function (user) {
// For both client and server side, we have a few classes that
// mean very specific things. NotFound, Redirect, etc. When we
// get the defaultHandler in the client, we can use it to handle
// NotFound. We can also handle Redirect using replaceWith. On the
// server these would be handled by 404 and 30x respectively.
if (!user)
return new Router.NotFound('User with id ' + params.userID + ' was not found');
// We don't reserve the "dataProvider" or "loading" keys in state, or use
// a special isResolved() method. Just use loading* state variables as
// you've always done.
setState({
loadingUser: false
});
// EDIT: We automatically use this value as `this.state.user`.
return user;
}),
// Streaming data! I've actually got this use case right now where
// I'm using streaming XHR to load images.
stream: getStreamingData(params.userID, function (chunk) {
streamData += chunk;
// Do progress with callbacks, not promises.
setState({
streamReceivedSoFar: streamData
});
})
};
}
},
getInitialState: function () {
// Use state variables to track when data is loaded, like you usually do.
return {
loadingUser: true,
streamReceivedSoFar: ''
};
},
render: function () {
if (this.state.loadingUser)
return <LoadingUser/>;
return (
<div>Welcome {this.state.user.name}! So far, you've received {this.state.streamReceivedSoFar.length} data!</div>
);
}
});
This also doesn't require a mixin. We just use if (typeof handler.fetchData === 'function')
to tell if the route needs to be rendered asynchronously or not in renderRoutesToString
.
from react-router.
ooh, I like the way this is going. You're exactly right, we're just after setting state whenever we feel like it. This api lets somebody implement dataProviders
the way I described them, but is far more flexible. <3 it.
s/fetchState
/getInitialAsyncState
/?
Regarding server sent 404s and such, we could maybe give the user a chance to send any arbitrary data to the resolved value, and let them do whatever they want instead of some pre-baked NotFound stuff:
Router.renderRoutesToString(routes, url).then(function(result) {
// result.html
// result[userDefined]
serverResponse.send(result.code, result.html);
});
from react-router.
@mjackson so that's a very thin layer which solves the server-side rendering question, but avoids caching, data providers, etc. Which I guess is all that's really needed -- the fancier stuff can be handled by a separate lib
from react-router.
so that's a very thin layer which solves the server-side rendering question, but avoids caching, data providers, etc.
What do you mean? The intent is to provide an API that solves both client and server needs.
from react-router.
@rpflorence Yeah. We should def call it getInitialAsyncState
.
Re: not found, we could handle it on both the client and the server with the <Routes defaultHandler>
hook we discussed in #112.
from react-router.
I am leaning towards the more explicit dataProviders
syntax because it gives the framework more information about what data you are fetching. I'm not sure, but I think it would allow better caching and other data niceties.
from react-router.
I think it would allow better caching
Having routes nested inside one another already gives us some level of caching for free. Nested routes may inherit data from their parent routes, and we don't unload parent routes when transitioning between children, so they preserve state.
from react-router.
With @mjackson API, my example is nearly identical, except that his feels more similar to react's getInitialState
and getDefaultProps
. Devs just return stuff from getInitialAsyncState
; all naming conventions are the same with stuff everybody already knows.
Equivalent to my earlier example:
var User = React.createClass({
statics: {
getInitialAsyncState: function (params, query) {
return {
user: getUserByID(params.userID),
activity: getActivityByUserId(params.userID)
};
}
},
render: function() {
var user = this.state.user ?
<LoadingUser/> :
<User user={this.state.user}/>;
var activity = this.state.activity ?
<LoadingActivy/> :
<Activity activity={this.state.activity}/>;
return (
<div>
{user}
{activity}
</div>
);
}
});
I am leaning towards the more explicit dataProviders syntax
dataProviders
syntax isn't any more or less explicitgetInitialAsyncState
is more idiomatic to what we already know in react- you could build in the
dataProviders
convention withgetInitialAsyncState
and a mixin if you prefer that API, but you could not go the other way around (like the streaming case).
from react-router.
Also, if you need access to the promises you just do what you need to do in getInitialAsyncState
, and with a setState
function you are free to do nearly anything.
My question is, how do we know when everything is done? Like in the streaming case, how does the router know you're all done for renderRoutesToString
?
from react-router.
@rpflorence I meant object (key, function), which is statically analyzable, instead of a single function. But I think this is fine.
I assumed that the streaming case returned a promise, but also had a callback for incremental update.
from react-router.
@mjackson you did setState
in the success handler of getUserById
, if its a promise, can't we just do that automatically?
edit nvm: you did loadingUser
, not user
, so it appears your API will indeed set this.state.user
automatically.
from react-router.
@rpflorence getStreamingData
returns a promise that resolves when the stream is finished, so that's how you know when everything is done.
your API will indeed set this.state.user automatically
Yeah, but I made a mistake. The promise needs to resolve to the user object in order to do that, so I need to return user
. I've updated the example.
from react-router.
This API also lets you load data that depends on other async data.
getInitialAsyncState: function (params, query, setState) {
return {
post: getPost(params.postID).then(function (post) {
// I'm sure there's a better example out there, but you get the idea.
return getComments(post.commentIDs).then(function (comments) {
setState({
comments: comments
});
return post;
});
});
};
}
from react-router.
I meant object (key, function), which is statically analyzable
@jaredly I know the React devs are planning on eventually being able to statically analyze component props (instead of using React.PropTypes
), but I haven't heard about any similar plans for this.state
or getInitialState
. If/when that happens, getInitialAsyncState
can follow suit.
from react-router.
alright, let's
React.CreateClass({
mixins: [Router.AsyncStateMixin],
statics: {
getInitialAsyncState: function(params, query, setState) {
return {
someKeyThatBecomesState: valueOrPromise
}
}
}
});
I think we still need a mixin so that we can call that stuff on the client in componentDidMount
.
from react-router.
There was some talk of having an asyncDataDidLoad
function that was called once one of the promises resolved, with (name, data)
. Is that no longer?
from react-router.
oh right, componentDidReceiveAsyncState(key, data)
I imagine this being used to set up polling, or to retry if the operation failed. Those two use-cases can be easily solved with setState
and your own success/failure handlers in getInitialAsyncState
though.
I'm happy to still have it, just wish I could think of how anybody would use it...
from react-router.
My use case is initializing some other state based on the data that was loaded. I guess I can just have a custom success handler in the getInitialAsyncState.
from react-router.
lets continue this thread (componentDidReceiveAsyncState
) over at #138
from react-router.
Is there a way to pass some context to getInitialAsyncState
? In my use case I have a multi-tenant application on the server side which needs access to the tenant
object every time it fetches data.
I need something like this:
var ChatSidebar = React.CreateClass({
mixins: [Router.AsyncStateMixin],
statics: {
getInitialAsyncState: function(params, query, setState, context) {
var channelStore = context.getStore("channel")
, userStore = context.getStore("user");
return {
channels: channelStore.fetch(),
users: userStore.fetch({status: 'active'});
}
}
}
});
app.get('/chat', function(req, res) {
var context = createContext({req: req, tenant: req.tenant});
Router.renderComponentToString(App({context: context}), function(markup) {
res.send(markup);
});
});
Other use cases would include code that depends on locale or session data.
from react-router.
What about something like this?
// current-context.js
var _currentContext = null;
exports.set = function(context) {
_currentContext = context;
};
exports.get = function() {
return _currentContext;
};
var Context = require('./current-context');
app.get('/chat', function(req, res) {
Context.set(createContext({req: req, tenant: req.tenant}));
Router.renderComponentToString(App({context: context}), function(markup) {
res.send(markup);
});
});
var Context = require('./current-context');
var ChatSidebar = React.CreateClass({
mixins: [Router.AsyncStateMixin],
statics: {
getInitialAsyncState: function(params, query, setState) {
var context = Context.get();
var channelStore = context.getStore("channel")
, userStore = context.getStore("user");
return {
channels: channelStore.fetch(),
users: userStore.fetch({status: 'active'});
}
}
}
});
Obviously the current-context.js
is too naive for something multi-tenant, but I think it demonstrates the idea that you can use a module to keep track of what the current context is for a request. I also have questions around how your code would work on the client.
from react-router.
You know how i love globals :) I'd be on favor of having a context that's
managed by the router
On Jul 29, 2014 6:21 AM, "Ryan Florence" [email protected] wrote:
What about something like this?
// context.jsvar _currentContext = null;exports.set = function(context) {
_currentContext = context;};exports.get = function() {
return _currentContext;};var Context = require('./context');
app.get('/chat', function(req, res) {
Context.set(createContext({req: req, tenant: req.tenant}));
Router.renderComponentToString(App({context: context}), function(markup) {
res.send(markup);
});});var Context = require('./context');
var ChatSidebar = React.CreateClass({
mixins: [Router.AsyncStateMixin],statics: {
getInitialAsyncState: function(params, query, setState) {
var context = Context.get();
var channelStore = context.getStore("channel")
, userStore = context.getStore("user");return { channels: channelStore.fetch(), users: userStore.fetch({status: 'active'}); } }
}});
—
Reply to this email directly or view it on GitHub
#57 (comment).
from react-router.
@madebyherzblut How does your app determine what the context is (i.e. which tenant)?
If it's based on something in the URL (e.g. /:tenantID/dashboard
) then you can use params.tenantID
to setup context inside getInitialAsyncState
.
Otherwise, if it's based on cookie/session data I'd recommend passing the context in through to your view hierarchy as a prop as @rpflorence suggests.
from react-router.
A real implementation would manage a set of contexts based on session.
Sent from my iPhone
On Jul 29, 2014, at 10:29 AM, Jared Forsyth [email protected] wrote:
You know how i love globals :) I'd be on favor of having a context that's
managed by the router
On Jul 29, 2014 6:21 AM, "Ryan Florence" [email protected] wrote:What about something like this?
// context.jsvar _currentContext = null;exports.set = function(context) {
_currentContext = context;};exports.get = function() {
return _currentContext;};var Context = require('./context');
app.get('/chat', function(req, res) {
Context.set(createContext({req: req, tenant: req.tenant}));
Router.renderComponentToString(App({context: context}), function(markup) {
res.send(markup);
});});var Context = require('./context');
var ChatSidebar = React.CreateClass({
mixins: [Router.AsyncStateMixin],statics: {
getInitialAsyncState: function(params, query, setState) {
var context = Context.get();
var channelStore = context.getStore("channel")
, userStore = context.getStore("user");return {
channels: channelStore.fetch(),
users: userStore.fetch({status: 'active'});
}
}
}});—
Reply to this email directly or view it on GitHub
#57 (comment).—
Reply to this email directly or view it on GitHub.
from react-router.
Yeah --- I think it's way out of scope to put together a full context
manager -- that's the job of express or whatever framework. The Router
can just support initialization with some context which get's passed
through to the routes.
On 7/29/14, Ryan Florence [email protected] wrote:
A real implementation would manage a set of contexts based on session.
Sent from my iPhone
On Jul 29, 2014, at 10:29 AM, Jared Forsyth [email protected]
wrote:You know how i love globals :) I'd be on favor of having a context that's
managed by the router
On Jul 29, 2014 6:21 AM, "Ryan Florence" [email protected] wrote:What about something like this?
// context.jsvar _currentContext = null;exports.set = function(context)
{
_currentContext = context;};exports.get = function() {
return _currentContext;};var Context = require('./context');
app.get('/chat', function(req, res) {
Context.set(createContext({req: req, tenant: req.tenant}));
Router.renderComponentToString(App({context: context}), function(markup)
{
res.send(markup);
});});var Context = require('./context');
var ChatSidebar = React.CreateClass({
mixins: [Router.AsyncStateMixin],statics: {
getInitialAsyncState: function(params, query, setState) {
var context = Context.get();
var channelStore = context.getStore("channel")
, userStore = context.getStore("user");return {
channels: channelStore.fetch(),
users: userStore.fetch({status: 'active'});
}
}
}});—
Reply to this email directly or view it on GitHub
#57 (comment).—
Reply to this email directly or view it on GitHub.
Reply to this email directly or view it on GitHub:
#57 (comment)
from react-router.
I'd be on favor of having a context that's managed by the router
I think it's way out of scope to put together a full context manager
@jaredly ? Did you change your mind? :)
from react-router.
=) no, I guess I just wasn't clear with my original comment. Perhaps "context that's passed through by the router" would be clearer.
from react-router.
I like the code example by @madebyherzblut; context is initialized outside of the router, but the router knows about it and passes it through to the routes and getInitialAsyncStates
from react-router.
You can pass "context" down to your routes with route props
// abstraction to be able to do this for all the routes your server responds to
function buildRoutes(context) {
return (
<Routes>
<Route handler={App} context={context}/>
</Routes>
);
}
app.get('/chat', function(req, res) {
var context = createContext({req: req, tenant: req.tenant});
Router.renderComponentToString(buildRoutes(context), function(markup) {
res.send(markup);
});
});
from react-router.
All that's really left to do on this issue is add a renderRoutesToString(routes, path)
method.
React's current implementation of renderComponentToString is sync, but we'll need to make ours async to resolve async state. In v0.8.0 React had a renderComponentToString that had an async API but it wasn't actually async.
Looking through these two implementations, it's not clear to me how to use the ServerRenderingTransaction
in a fully async way. For example, can I start a transaction in one turn of the event loop and finish it in another? If not, how should I go about this? If @petehunt or @spicyj has any input here I'd love to hear it.
If you're inclined to ask other questions about server-side rendering that aren't directly related to renderRoutesToString
, let's please discuss them in a separate issue to keep this comment thread from growing too large.
from react-router.
No, you can't start and end a Transaction in different ticks. The best approach is probably to fetch all the data async and pass it in in such a way that it the routes can be rendered synchronously after the data is fetched.
from react-router.
Related Issues (20)
- [Docs]: Tutorial missing imports within 'contacts.js'. [ "match-sorter" , "localforage" , "sort-by" ] HOT 1
- [Bug]: history.listen not triggered on hash changes HOT 1
- [Bug]: Update comments recommending to use polyfill.io HOT 3
- [Docs]: Nested Routes are essentially undocumented HOT 5
- [Bug]: unstable_patchRoutesOnMiss is superseded by "*" (404) HOT 3
- [Bug]: unstable_patchRoutesOnMiss leaf ignored when patching sub-trees asyncronously HOT 3
- [Bug]: Functional updates to `useSearchParams` don't get updated values HOT 2
- Update to isbot@5 HOT 1
- [Bug]: `unstable_patchRoutesOnMiss` remote module error-handling HOT 13
- [Bug]: useMatch does not decode params HOT 4
- [Bug]: Could not resolve './utils' from packages/router/index.ts HOT 2
- [Bug]: Link when used with Outlet is not working. It is routing to error page when clicked. HOT 1
- [Bug]: setSearchParams from useSearchParams remove hash HOT 1
- [Bug]: HashRouter need to wrapped twice HOT 1
- [Docs]: docs show reach/router instead of react/router HOT 2
- [Bug]: "react-router-dom-v5-compat" <CompatRouter> doesn't unsubscribe from history potentially causing tests to memory leak HOT 2
- [Bug]: child loader redirect overrides parent loader error HOT 7
- [Bug]: v7_partialHydration with unstable_patchRoutesOnMiss: hydrateFallbackElement not shown on patched routes HOT 3
- [Bug]: Relative path does not work as expected in outlets HOT 1
- [Bug]: useNavigate causes component to rerender HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-router.