Use React 16.3 lifecycle methods
thebuilder opened this issue · comments
Hey!
Since componentWillMount
is considered unsafe in the latest version of React, i looked into replacing it with componentDidMount
.
I've integrated the component into my project (rewritten it to use Flow types, and removed json2mq
since i didn't need it).
If you'd like, i can update the current module to follow this structure.
The biggest change is that i've added a ssr
prop. If this is false (default), the client will add the matcher in the constructor method. Otherwise, the match will not happen before componentDidMount
. This is to ensure the DOM doesn't change before the first render.
This change will eliminate the DOM doesn't match when rendering serverside.
facebook/react#10591
I don't think this will break existing solutions, since the ssr=false
behavior matches the current implementation.
I've also added support for componentDidUpdate
, so the query can be changed at runtime (hot reload).
// @flow
import * as React from 'react'
type MediaQueryList = {
matches: boolean,
addListener: (handler: Function) => void,
removeListener: (handler: Function) => void,
}
type Props = {
defaultMatches: boolean,
query: string,
render?: Function,
children?: React.Node | Function,
ssr?: boolean,
}
type State = {
matches: boolean,
}
/**
* Conditionally renders based on whether or not a media query matches.
* Adapted from: https://github.com/ReactTraining/react-media
*/
class MediaQuery extends React.Component<Props, State> {
static defaultProps = {
defaultMatches: true,
ssr: false,
}
constructor(props: Props) {
super(props)
this.state = {
matches: this.props.defaultMatches,
}
if (!props.ssr) {
// If not rendering serverside, add the matcher now so state reflects the correct query
this.addMatcher()
}
}
componentDidMount() {
if (!this.mediaQueryList) {
this.addMatcher()
}
}
componentDidUpdate(lastProps: Props) {
if (this.props.query !== lastProps.query) {
this.addMatcher()
}
}
componentWillUnmount() {
if (this.mediaQueryList) {
this.mediaQueryList.removeListener(this.updateMatches)
}
}
mediaQueryList: MediaQueryList
addMatcher() {
if (this.mediaQueryList) {
this.mediaQueryList.removeListener(this.updateMatches)
}
this.mediaQueryList = window.matchMedia(this.props.query)
this.mediaQueryList.addListener(this.updateMatches)
this.updateMatches()
}
updateMatches = () => this.setState({ matches: this.mediaQueryList.matches })
render() {
const { children, render } = this.props
const { matches } = this.state
return render
? matches
? render()
: null
: children
? typeof children === 'function'
? children(matches)
: !Array.isArray(children) || children.length // Preact defaults to empty children array
? matches
? React.Children.only(children)
: null
: null
: null
}
}
export default MediaQuery
It's not a good idea to add matcher in the constructor, as long componentWillMount
was removed as long Will does not mean Did. So the same rules should be applied to a constructor.
Take a look on react-media-matcher, which handles this moment more correctly.
class Media extends React.Component{
constructor(props: MediaProps) {
super(props);
if (typeof window !== "object" || !window.matchMedia) return;
this.mediaQueryList = window.matchMedia(this.props.query);
this.state.matches = this.mediaQueryList.matches; <-- just SET IT
}
updateMatches = () => this.setState({matches: this.mediaQueryList!.matches});
componentDidMount() {
if (this.mediaQueryList) {
this.mediaQueryList.addListener(this.updateMatches); <-- subscribe!
}
}
componentWillUnmount() {
if (this.mediaQueryList) {
this.mediaQueryList.removeListener(this.updateMatches);
}
}
render() {
const {children} = this.props;
const {matches} = this.state;
return children(matches)
}
}
The thing is, you'll get an output mismatch if rendering serverside, when the query doesn't match the default match.
That's the reason for the ssr
prop. It adds the matcher if only rendering on the client.
In my case I do not use Media
component to render something, only to calculate match
. Next results for all Media
s for all my breakpoints got stored in context, and every renderer
read value not from Media, but from context.
For SSR you may put any custom value into that context, and thus put application render into the desired state.
Don't use Media directly - that's the only way to make your application a bit more controllable and predictable.
@thebuilder Thanks for the input! I'm closing this in favor of #81. If you feel like it, we'd appreciate a PR with the solution outlined in that issue.
P.S. I'm not sure how @mjackson feels about rewriting the library with static types 🙈