-
Notifications
You must be signed in to change notification settings - Fork 27k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to conditionally include packages on client but not server (and vice-versa)? #219
Comments
Conditional require is probably the way to go. That's not a Next error, that's a React error. React warns if you do an initial render to an element that already contains react component markup inside it which doesn't match exactly. You want to make sure that the first render on both the server and the client creates the exact same structure. So if you want to render something like a map on the client but not the server, don't do it until after the initial render. You can do this by setting some state in componentDidMount which causes a re-render with the actual content you want displayed (componentDidMount only gets run on the client) let mapLib;
export default class SomePage extends React.Component {
constructor () {
super();
this.state = { showMap: false };
}
componentDidMount () {
mapLib = require('some-map-library');
this.setState({ showMap: true });
}
render () {
return (
<div>
{
this.state.showMap ? (some sort of map thing) : "Loading map"
}
</div>
);
}
} On the above example, the first thing rendered on both the server and the client is P.S. The above is untested and I am not a Next developer, just a random guy trying to use it. |
That's some good hackery! Thanks! ... I'm going to leave this issue open in case there's a better way, but this gets me where I need to go! |
@mikecardwell using |
@sedubois That is quite obvious, isn't it? The alternative is to either ignore React warnings or hack React to drop the checksums validations (I think, the minified version of React drops these checks for you)... The choice is yours. |
It does trigger a second render. That's why this fixes the problem being discussed. "prop/layout thrashing" sounds much worse than it is. It makes sense to generally avoid calling setState in componentDidMount if you can do it elsewhere, e.g. componentWillMount, but in this case, we want to trigger a second render, and the first render is so tiny that it's not going to hit performance. He could even stick this at the top of his render function: if (!this.state.showMap) return null; I don't think we'll get much "thrashing" from rendering nothing to the page once. |
Yes it's obvious, but was less obvious that it's a "bad practice" as flagged by the eslint rule. Would just have been nice to render properly in one go. But yes, not a big deal, it's just hacky as mentioned. |
I don't think there's anything that could be done in the next framework to avoid this "hack". It's entirely a React issue. I've had to do stuff like this in the past when it comes to dates. Imagine the following component: const component = () => <p>{ Date.now() }</p>; That will trigger this exact same problem. The initial render on the server will show one date and the initial render on the client will almost certainly show a different one unless the server and client clocks match to the millisecond, taking latency between server/client render into account. |
@evantahler You could use react-no-ssr: import React from 'react';
import NoSSR from 'react-no-ssr';
import Comments from './comments.jsx';
const MyPage = () => (
<div>
<h2>My Blog Post</h2>
<hr />
<NoSSR>
<Comments />
</NoSSR>
</div>
); In the above example, comment will only render on the client. |
@arunoda again, it's not a problem of controlling rendering, it's a problem of |
@evantahler Okay. got it. With SSR, it won't be another render. For me this is totally how it should be done. |
I think we can close this now. Additionally, if you'd like to include a module only on server, you can conditionally require and exclude it from a bundle file for client like the following (v2 or later). // pages/index.js
const isServer = typeof window === 'undefined'
const foo = isServer ? require('foo') : null // next.config.js
module.exports = {
webpack (config) {
config.externals = config.externals || {}
config.externals.foo = 'foo'
return config
}
} |
I'm facing a similar issue as @evantahler with react-leaflet. The fix proposal (mounting map only on client side) seems a good approach, but I cannot manage to make it work. The error is : Did you encounter this error guys? |
I could probably diagnose your problem if I saw your code. Going on the error message alone, I haven't a clue. |
yes of course! the revelant part is here : let Map, TileLayer
export default class LeafletWrapper extends React.Component {
constructor () {
super()
this.state = { showMap: false }
}
componentDidMount() {
let RL = require('react-leaflet')
Map = RL.Map
TileLayer = RL.TileLayer
this.setState({ showMap: true })
}
render () {
return this.state.showMap ? (
<Map
center={[48.85692, 2.35268]}
zoom={13}
>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
/>
</Map>
) : <div>Loading map</div>
}
} and an extract of the dependencies in my package.json: {
"dependencies": {
"next": "2.0.0-beta.24",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"leaflet": "^1.0.3",
"react-leaflet": "^1.1.0"
...
}
} thanks for interest! |
I just created a basic next app using your code from above and it worked for me with no errors in the console. I then added an additional buitton to allow me to toggle the showMap state manually so I could make sure everything worked fine as the map was mounted/unmounted, and there were no issues at all. Any chance you could make a minimal example of this problem happening which I could install on my local system and see the error myself? [edit] I wasn't using Next 2 actually. Brb [edit 2] Hmm. I get the same issue with Next 2. I'm stumped. I haven't actually looked into Next 2 previously though so presumably something has changed which I'm not aware of. One interesting thing to note is that if you keep the Map, but lose the TileLayer, you don't get the error message. I'm not suggesting that as a solution, just a data point ;) |
@kaelhem Not related. But |
You're totally right. But this is not the problem here. |
@mikecardwell ha, this is a good catch. I will have look during this weekend... |
for interested people, I've open a ticket on stackoverflow where I describe a workaround. It seems like a bug in react-leaflet. |
The solution offered by @mikecardwell works, but mixing the require syntax and import syntax feels like a hacky and short-lived solution. Maybe this issue should be reopened in order to prompt next.js to implement a better solution? |
Shopify/buy-button-js#381 is the same issue. Thanks for the |
The |
@chemzqm In the meantime, support for dynamic import has been merged. Dynamic import without SSR could help here I think. |
Like your solution @mikecardwell However, now I'm getting this error: Do not use setState in componentDidMount Edit Change To |
Hi everyone, I used the react-leaflet code normally in my use_map.js file I created. And I imported this file in another file with next/dynamic and its nossr option and I called it in render function. And it's worked for me :) const DynamicComponentWithNoSSR = dynamic(import('../components/hello3'), { |
@mikecardwell You save my life! |
Be aware of silenced errors in nossr dynamic components: #2898 |
What you should note with this approach: It's basically the opposite of what you want when you're using SSR. In my use case I'm using SSR for guaranteed SEO, but when I use react-no-ssr then of course it'll skip this part from server side rendering resulting in missing content. I need – for example – a workaround to use ScrollMagic (which depends on
I totally agree. |
i wrote special component to use Spectacle // prettier-ignore
const spectacleExports = [ "Anim", "Appear", "BlockQuote", "Cite", "CodePane", "Code", "ComponentPlayground", "Deck", "Fill", "Fit", "Heading", "Image", "GoToAction", "Layout", "Link", "ListItem", "List", "Magic", "Markdown", "MarkdownSlides", "Notes", "Quote", "S", "Slide", "SlideSet", "TableBody", "TableHeader", "TableHeaderItem", "TableItem", "TableRow", "Table", "Text", "Typeface", "themes" ];
const setToFalse = (state, key) => ({ ...state, [key]: false });
const setTo = spectacle => (state, key) => ({
...state,
[key]: spectacle[key]
});
const isReady = o => Object.values(o).every(x => !!x);
class WithSpectacle extends React.Component {
constructor(props) {
super(props);
this.state = spectacleExports.reduce(setToFalse, {});
}
componentDidMount() {
const spectacle = require("spectacle");
this.setState(spectacleExports.reduce(setTo(spectacle), {}));
}
render() {
if (!isReady(this.state)) return null;
return this.props.render(this.state);
}
} |
FWIW We had a similar issue with a PDF rendering library and used this helper to work around this issue. Our problem was that rendering anything which depends on PDF.js server-side will throw errors, as the library itself contains browser API calls (related to web workers) which are not safe in Node. By using the above helper to import the component that handles PDF rendering we were able to defer parsing of the library code to the browser, where it ran without error: import dynamic from 'next/dynamic';
// This component depends on the client-only PDF library
const PDFViewer = dynamic(import('../../components/PDFViewer'), { ssr: false });
class ExampleClass extends Component {
render() {
return (
<div>
<PDFViewer />
</div>
);
}
} |
@felizuno But how does that work with node modules (located in |
Say you wanted to render some maps in your Next application. You chose the fairly popular
react-leaflet
project, which takes the very popular leaflet library... and React-izes it.It turns out that Leaflet is in fact not ready for an isomorphic application, as it has things like
window
andnavigator
all over its codebase. This is turn causes Next to crash with a sadReferenceError: window is not defined
.You can't use conditional logic in your map component because
include
statements need to happen at the top of the file, and at boot. You can change frominclude
torequire
(and require conditionally), but that seems like a bad idea, and Next throws a warning (below). You can block rendering a map on the server, but not the inclusion of thereact-leaflet => react
libraries.The warning Next throws when using a conditional
required
:It's fairly easy to check if you are running on the sever or client:
How should we handle something like this? I could imagine a
try/catch
scenario when rendering on the server looking for specificallywindow
,navigator
, etc and rendering a small./pages/_unrenderable.js
in those components.... but that seems... hacky.The text was updated successfully, but these errors were encountered: