ReactTraining / react-media

CSS media queries for React

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is this SSR ready out of the box?

daydream05 opened this issue · comments

I use Gatsby JS and was wondering if I need to do any modifications if I need this to work for SSR. Thanks

react-media probably would not work with SSR, as long it does not provide methods to mock environment, and not match anything if you dont have window object (and you dont)

If you are looking for SSR-friendly media-matcher - take a look on https://github.com/thearnica/react-media-match

We use this lib with SSR, I explained our workflow here: #50 (comment)

@daydream05 I didn't read your original message properly. But getting this to work with Gatsby with your pre-rendered HTML will be impossible, since AFAIK Gatsby produces the HTML files at build time, at which point you have no information which device will be served.

I'm not familiar with Gatsby, but the only option I can think of is to render multiple versions of your site, but now you'll have to figure out how to serve the correct version to the correct user. You'll probably be better off with just picking a default (i.e. mobile), which will be rendered to your static HTML. And then let <Media> correct client-side if the default turns out to be a mismatch.

The problem with React-media+SSR is ... the absence of documentation.

We've just merged the PR with SSR docs!

I was having this same issue on a Gatsby site I had planned on launching today. The problem with Gatsby SSR is that the static pages are created at build time so there's no opportunity to evaluate the client hitting the server to setup defaultMatches correctly.

I don't have much SSR experience and don't know exactly how componentWillMount works on SS and then how it gets called again client side, but here's what I was experiencing: <Media> wasn't returning the correct match when the page first loads, and would only work if I started resizing the window. In other words, <Media> will listen to the window changing size but only after the component loads. This was only happening in production since local Gatsby doesn't serve SSR, which is why I didn't notice the issue until today (site launch day).

Again, I'm no SSR expert and don't know how your componentWillMount works SS vs CS, but I do know that componentDidMount only works client side, so I came up with a nasty trick that I'm not proud of :(

class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = { key: 0 }
  }

  componentDidMount() {
    this.setState({ key: 1 })
  }

  render() {
    return (
      <Media query="(max-width: 800px)" key={this.state.key}>
        {matches => {
          console.log(matches)
        }}
      </Media>
    )
  }
}

The premise of the trick (for those finding this and don't see it) is that componentDidMount only fires client side and not server side so by swapping out the key value as soon as my component loads on the client will force <Media> to re-evaluate the window size which means it doesn't matter how Gatsby or other SSR's give you the initial HTML, this will self-correct as soon as the page loads

However

This got me thinking about what if I changed <Media>'s code itself to use componentDidMount instead of componentWillMount. I copied the code for Media to be a local component on my project, changed Will to Did, published it into production and everything works. Since componentDidMount is client only, <Media> sets up its first evaluation client side, not SSR.

I went to fork and change and ran tests and I get one test that fails which I'm not sure how you want to solve. Here's the test line number

I think it fails because with componentDidMount, the state for matches is this.props.defaultMatches which is true by default, so since componentDidMount happens later than componentWillMount, a render gets called with this.state.matches being true before componentDidMount can set it to false.

In any case, this whole problem is fixed if we can change to componentDidMount. Sorry this is long

Hi @bradwestfall 👋, thanks for your detailed write-up.

First of all, we are planning to move to componentDidMount already.

[I] ... don't know exactly how componentWillMount works on SS

componentWillMount does fire on the server. However, it will be deprecated in future versions, so relying on it would be unwise.

<Media> will listen to the window changing size but only after the component loads

I think I figured this one out. Since componentWillMount is called before render, calling setState here will not trigger a rerender. Now since setState is not actually synchronous - you're queuing a state change - the change might only be reflected after the render has completed. Normally this is no problem, as the state change would subsequently trigger a rerender, but it doesn't from componentWillMount.

I guess this problem is one of the motivations of deprecating componentWillMount, and it's another reason for use to move to componentDidMount 😄

Thanks for the fast response. Any idea on a timeframe?

Sorry, can't be sure about a timeline. @mjackson is a busy man, and I'd like to pass these changes by him before publishing.