diff --git a/README.md b/README.md index 90090fa..69c5cb4 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,30 @@ Keys of media query objects are camel-cased and numeric values automatically get An optional `targetWindow` prop can be specified if you want the `query` to be evaluated against a different window object than the one the code is running in. This can be useful for example if you are rendering part of your component tree to an iframe or [a popup window](https://hackernoon.com/using-a-react-16-portal-to-do-something-cool-2a2d627b0202). +There is also an optional `onChange` prop, which is a callback function that will be invoked when the status of the media query changes. This can be useful if you need to do some imperative stuff in addition to the declarative approach `react-media` already provides. + +```jsx +import React from "react"; +import Media from "react-media"; + +class App extends React.Component { + render() { + return ( +
+ + matches + ? alert("The document is less than 600px wide.") + : alert("The document is at least 600px wide.") + } + /> +
+ ); + } +} +``` + If you're curious about how react-media differs from [react-responsive](https://github.com/contra/react-responsive), please see [this comment](https://github.com/ReactTraining/react-media/issues/70#issuecomment-347774260). Enjoy! diff --git a/modules/Media.js b/modules/Media.js index da71686..1ca8ded 100644 --- a/modules/Media.js +++ b/modules/Media.js @@ -16,7 +16,8 @@ class Media extends React.Component { ]).isRequired, render: PropTypes.func, children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - targetWindow: PropTypes.object + targetWindow: PropTypes.object, + onChange: PropTypes.func }; static defaultProps = { @@ -27,7 +28,16 @@ class Media extends React.Component { matches: this.props.defaultMatches }; - updateMatches = () => this.setState({ matches: this.mediaQueryList.matches }); + updateMatches = () => { + const { matches } = this.mediaQueryList; + + this.setState({ matches }); + + const { onChange } = this.props; + if (onChange) { + onChange(matches); + } + }; componentWillMount() { if (typeof window !== "object") return; @@ -56,12 +66,16 @@ class Media extends React.Component { const { matches } = this.state; return render - ? matches ? render() : null + ? 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 + ? matches + ? React.Children.only(children) + : null : null : null; } diff --git a/modules/__tests__/Media-test.js b/modules/__tests__/Media-test.js index 49678d6..0ab65e2 100644 --- a/modules/__tests__/Media-test.js +++ b/modules/__tests__/Media-test.js @@ -160,6 +160,21 @@ describe("A ", () => { }); }); + describe("when an onChange function is passed", () => { + beforeEach(() => { + window.matchMedia = createMockMediaMatcher(true); + }); + + it("calls the function with the match result", () => { + const callback = jest.fn(); + const element = ; + + ReactDOM.render(element, node, () => { + expect(callback).toHaveBeenCalledWith(true); + }); + }); + }); + describe("rendered on the server", () => { beforeEach(() => { window.matchMedia = createMockMediaMatcher(true);