preact-router
preact-router copied to clipboard
Match and Link don't work server side with render-to-string
I'm using preact-router in a component that can be rendered client or server side.
Server side, I set the url= prop on the router, and it makes the correct children get rendered. But Match (and therefore Link) don't take this into account. Simple repro:
import {h} from 'preact';
import Router from 'preact-router';
import Match from 'preact-router/match';
import render from 'preact-render-to-string';
const vdom = (
<Router url="/foo">
<div path="/foo">
Path works
<Match path="/foo">
{ ({matches, path, url}) => (
<div>
<p>Matches: {matches || '?'}</p>
<p> Path: {path || '?'}</p>
<p>Url: {url || '?'}</p>
</div>
) }
</Match>
</div>
<div default>
Path doesn't work.
</div>
</Router>
);
console.log(render(vdom));
formatted output:
<div path="/foo" url="/foo" matches="[object Object]">Path works
<div>
<p>Matches: ?</p>
<p> Path: ?</p>
<p>Url: ?</p>
</div>
</div>
Would be nice if either:
- The URL from the router would bubble down or
- There was some other way to set the URL that worked server side
Am I missing an obvious right way to do this? If not, if somebody has a strong idea of how this should work I could offer a PR
Agreed! It would be nice if the URL bubbled down for sure. The only workaround I'm aware of right now is to pass a custom history to Router to tell is the location:
const customHistory = {
getCurrentLocation: () => "/foo"
};
const vdom = (
<Router url="/foo" history={customHistory}>
<div path="/foo">
Path works
<Match path="/foo">
{ ({matches, path, url}) => (
<div>
<p>Matches: {matches || '?'}</p>
<p> Path: {path || '?'}</p>
<p>Url: {url || '?'}</p>
</div>
) }
</Match>
</div>
<div default>
Path doesn't work.
</div>
</Router>
);
Thanks, this is a big help! I had to tweak it a little to make it work:
const customHistory = {
location: {pathname: "/foo"} // <-- CHANGED
};
const vdom = (
<Router url="/foo" history={customHistory}>
<div path="/foo">
Path works
<Match path="/foo">
{ ({matches, path, url}) => (
<div>
<p>Matches: {matches || '?'}</p>
<p> Path: {path || '?'}</p>
<p>Url: {url || '?'}</p>
</div>
) }
</Match>
</div>
<div default>
Path doesn't work.
</div>
</Router>
);
But that does the trick!
Awesome! Let's keep this issue open to see if we can make it work without the custom history.
Hey guys,
Stumbled upon this, just now. Seems like this should've not work, to start with. There's a customHistory.listen which is needed here: https://github.com/developit/preact-router/blob/master/src/index.js#L191 and missing from the above examples. Trying to wrap my head around it, can you guys clear it up? Do I need the .listen to be defined? or maybe the listen method should be optional?
Cheers!
cc @developit sorry for the ping 😄
.listen() can just be an empty function - SSR is a single-pass render, so listening can't respond anyway:
const customHistory = {
location: { pathname: "/foo" },
listen: () => {}
};
And shouldn't even need that, given you won't hit componentDidMount for a static render?
Hey guys, thanks for getting back!
@developit did that in the end, but the problem that I'm having right now, with undom and your vdom serialising gist, is that the activeClassName is not set on the first render.
@cjbest indeed with preact-render-to-string you won't have that called, but with undom, you pretty much hit it.
Gonna do some more digging and see if I can zero in the issue.
Thanks again!
Alright, so I think I have some answers. I have the following:
<div>
<NavigationWithLinks links={['/', '/my-page']} />
<Router history={myCustomHistory}>
<MyHomePage />
<MyPage />
</Router>
</div>
I think, the Link when going over the matches, does not have the customHistory yet, since that is set, globally, via the Router constructor.
I used the Link component outside of the Router. Is this not ok? I tried to avoid re-rendering/re-instantiating Link components, but I don't think that would happen, ever, since preact reuses the components. Paint flashing in devtools and console.logs proved it 😃.
Now, would it make sense to be able to use Link outside the Router? Wondering if we could have a container component, that could wrap your App element and that would set the global custom history? Does it make sense?
<CustomHistoryContainer history={history}>
<NavigationWithLinks links={['/', '/my-page']} />
<Router>
<MyHomePage />
<MyPage />
</Router>
</CustomHistoryContainer>
Looking forward, cheers!