Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 104 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,122 @@
# Responsive Component
# React ResponsiveRenderIf

A responsive React component that takes a media query and renders its children only if the query matches. Handles changes if you resize your browser or flip your device...
A responsive React component that takes a media query and renders its children only if the query matches. Handles changes if you resize your browser or flip your device.

## Implementation
### Implementation

Works natively with `Window.matchmedia` api that takes a media query and returns a `MediaQueryList` object representing the parsed result. [read more](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)
Works natively with `Window.matchMedia` api that takes a media query and returns a `MediaQueryList` object representing the parsed result. [read more](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia)

| Feature | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
|:----------:|:-------------:|:------:|:-------------:|:------:|:-------------:|
| Basic support | 9 | 6.0 (6.0) | 10 | 12.1 | 5.1 |
It works natively with the following browsers:

| Chrome | Firefox | Internet Explorer | Opera | Safari | Safari Mobile | Android |
|:----------:|:-------------:|:------:|:-------------:|:------:|:-------------:|:-----:|:-----:|
| 9 | 6.0 | 10 | 12.1 | 5.1 | 3.0 | 5 |

For older browsers, it falls back to Paul Irish's implementation of `matchMedia`: [read more](https://github.com/paulirish/matchMedia.js/)

### How to use this awesome piece of code?

[Try it out on codepen](http://codepen.io/jfkhoury/pen/MegmwV)

All you need to do is wrap your content (React Components or jsx or html...) for the specific screen/device you are trying to target, and set the media query accordingly:
First install it.
```
$ npm i react-responsive-render-if --save
```

Then `import` into your code as follows (you _are_ using ES6, right? :smirk:):
```javascript
import { ResponsiveComponent } from "react-responsive-component";
...
<ResponsiveComponent query="only screen and (max-width: 480px)">
<HamburgerBtn userId={userId} />
</ResponsiveComponent>
import ResponsiveRenderIf from "react-responsive-render-if";
```

Then all you need to do is wrap your content (JSX or HTML)
and set the `query` for the specific media you are trying to target.
You can use any media query supported by your browser. For a complete listing
see the [Media Queries Level 4](https://drafts.csswg.org/mediaqueries/) spec.

Here we display a different message depending on your screen width:
```html
<ResponsiveRenderIf query="screen and (max-width: 480px)">
You are reading this on a narrow browser window or a mobile device.
</ResponsiveRenderIf>

<ResponsiveComponent query="only screen and (min-width: 480px)">
<ProfileDropDown userId={userId} />
</ResponsiveComponent>
<ResponsiveRenderIf query="screen and (min-width: 480px)">
Ahh, look at all of this screen real estate!
</ResponsiveRenderIf>
```

#### Optional props
Or how about this cool trick. You don't want some area of your app to be printed? No problem.
```html
<ResponsiveRenderIf query="screen">
You are reading this on your screen.
</ResponsiveRenderIf>

You can pass a `tag` props to specify the tagname of the responsive component wrapper.
<ResponsiveRenderIf query="print">
Sorry, but you can't print this.
</ResponsiveRenderIf>
```

```javascript
<ResponsiveComponent query="tv" tag="ul">
<li>This feature is not supported on TVs yet :(</li>
</ResponsiveComponent>
### Optional props

#### `masqueradeAs`

You can pass a `masqueradeAs` to specify the component to "masquerade as" (i.e. to render as).
If you don't, ResponsiveRenderIf will render a "div", but only when necessary.
'masqueradeAs' can either be a string (such as "div" or "ul"), or a component (like "MyButton").

Let's say you had a component that you used as follows:
```html
<MyButton text="submit" />
```
The following will render the same output, but only when not bing printed
(who needs to print a button on the printer anyway?)
```html
<ResponsiveRenderIf query="not print" masqueradeAs="MyButton" text="submit" />
```

If you don't specify a `masqueradeAs` AND don't specify additional props AND
there is but a single child element, ResponsiveRenderIf adds no additional markup! In other words,
you pay no price (DOM wise) for using ResponsiveRenderIf. :tada:

For example, the following will NOT render a wrapping div as it wraps a single child element:
```html
<ResponsiveRenderIf query="screen">
<div>
<p>It was the best of times.</p>
<p>It was the worst of times.</p>
</div>
</ResponsiveRenderIf>
```
but this will:
```html
<ResponsiveRenderIf query="screen">
<p>It was the best of times.</p>
<p>It was the worst of times.</p>
</ResponsiveRenderIf>
```
Actually, the two examples above will render the exact same output. The second will wrap the multiple children
masquerading as a "div".

You can even pass additional props to the masqueraded component:
```html
<ResponsiveRenderIf query="screen" masqueradeAs="ol" start="4" classname="my-list">
<li>This is item four</li>
<li>This is item five</li>
</ResponsiveRenderIf>
```
which will render the following:
```html
<ol start="4" classname="my-list">
<li>This is item four</li>
<li>This is item five</li>
</ol>
```

### Try it out Live!
See it live and in action [on codepen](http://codepen.io/jfkhoury/pen/MegmwV).

### More Examples

What if your code works best when displayed in landscape mode?
```html
<ResponsiveRenderIf query="screen and (orientation:portrait)">
Please turn your device for optimal viewing
</ResponsiveRenderIf>
```
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"version": "1.0.8",
"name": "react-responsive-component",
"version": "2.0.0-alpha.1",
"name": "react-responsive-render-if",
"description": "A responsive React component that reacts to media queries.",
"keywords": ["react", "reactjs", "responsive"],
"keywords": ["react", "reactjs", "responsive", "react-component"],
"main": "./lib/index.js",
"scripts": {
"prepublish": "npm run lint && npm run build",
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "matchmedia-polyfill";
import "matchmedia-polyfill/matchMedia.addListener";
import ResponsiveComponent from "./ui/ResponsiveComponent";
import ResponsiveRenderIf from "./ui/ResponsiveRenderIf";

export { ResponsiveComponent };
export default ResponsiveRenderIf;
38 changes: 23 additions & 15 deletions src/ui/ResponsiveComponent.js → src/ui/ResponsiveRenderIf.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

export default class ResponsiveComponent extends React.Component {
export default class ResponsiveRenderIf extends React.Component {
constructor(props) {
super(props);
this.state = { canRender: false };
Expand All @@ -22,7 +22,7 @@ export default class ResponsiveComponent extends React.Component {

_tryToRender(props = this.props) {
if (!props.query) {
throw new Error("ResponsiveComponent: `query` is a required prop!");
throw new Error("ResponsiveRenderIf: `query` is a required prop!");
}

this._dispose();
Expand All @@ -44,22 +44,30 @@ export default class ResponsiveComponent extends React.Component {
}
}

get _display() {
return React.createElement(this.props.tag,
{ className: "responsive-component" },
this.props.children);
_render() {
const { masqueradeAs, query, children, ...additionProps } = this.props;
const multipleChildren = children.length > 1;
const hasAdditionProps = Object.keys(additionProps).length;

if (multipleChildren || masqueradeAs || hasAdditionProps) {
const Component = masqueradeAs || "div";
return <Component {...additionProps}>{children}</Component>;
} else {
return this.props.children; // Render the single child without a wrapper.
}
}

render() {
return this.state.canRender ? this._display : null;
return this.state.canRender ? this._render() : null;
}
}

ResponsiveComponent.propTypes = {
query: React.PropTypes.string.isRequired,
tag: React.PropTypes.string
};

ResponsiveComponent.defaultProps = {
tag: "div"
};
if (process.env.NODE_ENV !== "production") {
ResponsiveRenderIf.propTypes = {
query: React.PropTypes.string.isRequired,
masqueradeAs: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.element
])
};
}