Skip to content

Instantly share code, notes, and snippets.

@hejrobin
Created May 16, 2017 11:57
Show Gist options
  • Save hejrobin/1158952d0b7a12a8dd7ad653c483aebf to your computer and use it in GitHub Desktop.
Save hejrobin/1158952d0b7a12a8dd7ad653c483aebf to your computer and use it in GitHub Desktop.
ES6 Component for conditional render contexts.
/* @flow */
/**
* @type ComponentProperties
*/
type ComponentProperties = { [ key : any ] : any }
/**
* @type ComponentNodeTree
*/
type ComponentNodeTree = React$Element<any>
/* @dependencies */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
/**
* @type RenderContextDefaults
*/
type RenderContextDefaults = {
children? : ComponentNodeTree,
refuteMatch : bool,
assert : bool
}
/**
* @type RenderContextProperties
*/
type RenderContextProperties = {
children? : ComponentNodeTree,
mediaQuery : string,
refuteMatch : bool,
assert : bool
}
/**
* @type RenderContextState
*/
type RenderContextState = {
mediaQueryMatch : bool
}
/**
* @const string TINY_SCREEN
*/
export const TINY_SCREEN = "only screen and (max-width: 384px)"
/**
* @const string SMALL_SCREEN
*/
export const SMALL_SCREEN = "only screen and (min-width: 385px) and (max-width: 768px)"
/**
* @const string MEDIUM_SCREEN
*/
export const MEDIUM_SCREEN = "only screen and (min-width: 769px) and (max-width: 1151px)"
/**
* @const string LARGE_SCREEN
*/
export const LARGE_SCREEN = "only screen and (min-width: 1152px)"
/**
* Full width surface component, wrapper for decks, cards and other UI components.
*
* @prop string componentClassNames
* @prop ComponentProperties componentProperties
*/
export default class RenderContext extends Component<RenderContextDefaults, RenderContextProperties, RenderContextState> {
/**
* @var RenderContextState state
*/
state : RenderContextState = {
mediaQueryMatch : true
}
/**
* @var bool screenChange
*/
screenChange : bool = false
/**
* @static ComponentProperties propTypes
*/
static propTypes : ComponentProperties = {
mediaQuery : PropTypes.string.isRequired,
refuteMatch : PropTypes.bool,
assert : PropTypes.bool
}
/**
* @static ComponentProperties defaultProps
*/
static defaultProps : ComponentProperties = {
children : null, // @NOTE Fix for return type in flow
refuteMatch : false,
assert : undefined
}
/**
* Updates scroll state of navbar.
*
* @param SyntheticEvent event
*
* @return void
*/
handleScreenChangeEvent = ( event : SyntheticEvent ) : void => {
if ( this.screenChange === false ) {
window.requestAnimationFrame(() => {
const mediaQueryMatch : bool = this.validateMediaQueryMatch();
this.setState({ mediaQueryMatch });
this.screenChange = false;
});
this.screenChange = true;
}
}
/**
* Validates whether or not current render context matches media query.
*
* @return bool
*/
validateMediaQueryMatch() : bool {
let mediaQueryMatch : bool = window.matchMedia(this.props.mediaQuery).matches;
if ( this.props.refuteMatch === true ) {
mediaQueryMatch = ( ! mediaQueryMatch )
}
return mediaQueryMatch;
}
/**
* Attaches screen changes events.
*
* @return void
*/
componentWillMount() : void {
window.addEventListener('resize', this.handleScreenChangeEvent);
window.addEventListener('orientationchange', this.handleScreenChangeEvent);
const mediaQueryMatch : bool = this.validateMediaQueryMatch();
this.setState({ mediaQueryMatch });
}
/**
* Detaches screen changes events.
*
* @return void
*/
componentWillUnount() : void {
window.removeEventListener('resize', this.handleScreenChangeEvent);
window.removeEventListener('orientationchange', this.handleScreenChangeEvent);
}
/**
* @prop bool validContext
*/
get validContext() : bool {
if ( this.props.assert !== undefined ) {
return ( this.state.mediaQueryMatch === true && this.props.assert === true );
}
return ( this.state.mediaQueryMatch === true );
}
/**
* @return ComponentNodeTree
*/
render() : ComponentNodeTree | null {
if ( this.validContext === true && this.props.children ) {
return this.props.children;
}
return null;
}
}
/* @flow */
/**
* @type ComponentProperties
*/
type ComponentProperties = { [ key : any ] : any }
/**
* @type ComponentNodeTree
*/
type ComponentNodeTree = React$Element<any>
/* @dependencies */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import RenderContext, { TINY_SCREEN } from './rendercontext'
export SomeComponent extends Component {
render() : ComponentNodeTree {
return (
<article className="component some__component">
<RenderContext mediaQuery={TINY_SCREEN}>
<p>Visible for tiny screens!</p>
</RenderContext>
<RenderContext mediaQuery={TINY_SCREEN} refuteMatch>
<p>Visible for any screen except tiny screen!</p>
</RenderContext>
<RenderContext mediaQuery={TINY_SCREEN} assign={getCurrentUser().isLoggedIn}>
<p>Visible for tiny screens if assign-property is true.</p>
</RenderContext>
</article>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment